数値比較におけるオブジェクトとプリミティブ型の不整合
Java 7で導入されたObjects.equalsメソッドはヌルセーフな比較を実現しますが、数値ラッパー型の取り扱いには注意が必要です。
問題の再現
以下のコードで異なる結果が発生します:
Integer numA = 1000;
System.out.println(numA == 1000); // true
System.out.println(Objects.equals(numA, 1000)); // false
Long numB = 1000L;
System.out.println(numB == 1000); // true
System.out.println(Objects.equals(numB, 1000)); // false
原因分析
比較演算子==では自動的な型変換が行われますが、Objects.equalsではラッパー型の厳密な型チェックが実行されます。具体的には:
- 整数リテラル
1000はデフォルトでInteger型と解釈される LongオブジェクトとIntegerオブジェクトは型が不一致のため等価と判定されない
Objects.equalsの実装は以下の通りです:
public static boolean equals(Object a, Object b) {
return a == b || (a != null && a.equals(b));
}
回避策
型を明示的に指定して比較を行います:
Integer numA = 1000;
System.out.println(Objects.equals(numA, Integer.valueOf(1000))); // true
Long numB = 1000L;
System.out.println(Objects.equals(numB, Long.valueOf(1000))); // true
静的コード解析ツール(SonarQubeなど)で型不一致を早期検出することを推奨します。
三項演算子におけるオートボクシングの罠
問題の再現
boolean flag = false;
Double valueX = 5.0;
Double valueY = null;
Double result = flag ? valueX * 2 : valueY; // NullPointerException
原因分析
三項演算子の型推論ルールにより:
- 演算結果が
doubleプリミティブ型と判定される nullのDoubleオブジェクトをdoubleValue()でアンボクシング試みる- ヌル参照で
NullPointerExceptionが発生
回避策
以下のいずれかの方法で対処します:
// パターン1: if文による明示的な分岐
Double result;
if (flag) {
result = valueX * 2;
} else {
result = valueY;
}
// パターン2: プリミティブ型を使用
double result = flag ? valueX * 2 : (valueY != null ? valueY : 0.0);
ジェネリクスの型消去が引き起こすデータ漏洩
問題の再現
以下のような実装で予期せず内部データが外部に漏洩するケース:
public class ProductDO extends BaseDO<Long> { /* 商品データ */ }
public class ProductVO { /* ビュー用データ */ }
public ProductVO convert(ProductDO source) {
ProductVO target = new ProductVO();
BeanUtils.copyProperties(source, target); // IDがコピーされない
return target;
}
原因分析
SpringのBeanUtils.copyPropertiesはリフレクションを使用しますが:
- ジェネリクスによる型情報は実行時に消去される
- 親クラスの
BaseDO<T>のidはObject型と認識される ObjectからLongへの自動変換が行われない
回避策
以下の方法で確実にプロパティをコピーします:
public ProductVO convert(ProductDO source) {
ProductVO target = new ProductVO();
target.setId(source.getId()); // 明示的な設定
target.setName(source.getName());
return target;
}
またはLombokの@Builderを使用したマッピングを推奨します。
Setの重複排除に必要なハッシュ実装
問題の再現
以下のような実装で重複データが排除されないケース:
Set<Item> items = new HashSet<>();
items.add(new Item("A100", "商品X"));
items.add(new Item("A100", "商品Y"));
System.out.println(items.size()); // 2(重複排除されない)
原因分析
デフォルトのObject.equals()とObject.hashCode()は:
- オブジェクト参照を比較する
- 異なるインスタンスは常に異なると判定される
回避策
アイテムクラスに以下の実装を追加します:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return Objects.equals(code, item.code);
}
@Override
public int hashCode() {
return Objects.hash(code);
}
またはMapを用いたコードベースでの重複管理が効果的です:
Map<String, Item> itemMap = new HashMap<>();
itemMap.put(item.getCode(), item);