はじめに
仕事でJavaを使っていると,等価評価にequalsを頻繁に使うようになる.
新人のときは,「等価はequalsを使え!」と耳にタコが出来るほど聞いたものです.
しかし,これはString型をはじめとしたオブジェクト型の話である.
ここでプリミティブ型を持つint/Integerやlong/Longなどの場合で気になることがあった.
それは「Long型はオブジェクト型なんだから普通に==を使って比較できないんじゃね?」と言う疑問が出てきた.
手軽な疑問なので,検証してみました.
比較するパターンの洗い出し
対象はプリミティブ型を持つLong型にして,比較方法は==のみ使います.
左側にLong型インスタンスを右側に様々な方法でLong型を指定していく感じです.
あと,long定数とLong型の比較です.
さっと思いついたパターンだけですので,物足りないかもしれないです.
いざ検証!!
検証は以下のメソッドをJunit4で動かして確認しました.
バージョンに依存しないと思いますが,念のためJava8を使いました,と言っておきます.
「Long型はオブジェクト型である」という前提の元,検証したものもあります.
@Test
public void testEqualsMethod() {
Long value1 = 100L;
// いずれの方法で作ったLong型でも等価と評価される
assertTrue(value1==100L); // (1)
assertTrue(value1==Long.parseLong("100")); // (2)
assertTrue(value1==Long.valueOf("100")); // (3)
assertTrue(value1==Long.valueOf(100)); // (4)
assertTrue(100L==Long.parseLong("100")); // (5)
assertTrue(100L==Long.valueOf(100)); // (6)
Long value2 = value1; // Long型がオブジェクト型だとすると
++value2; // value2の値を変えれば,value1の値も変化するはず...
assertTrue(100L==value1); // しかし,value1は変化しない
assertTrue(101L==value2); // value2はしっかりと1増えてる
assertTrue(value1!=value2); // もちろんオブジェクトが違う...らしい
}
考察
上記のテストメソッドから,いずれの方法でLong型を生成すると==を使って等価評価が出来るということが分かった.
しかし「Long型->オブジェクト型->等価はequals」の感覚が消えない...
また,Long型を別のオブジェクトに代入してもオブジェクト型の共有するような挙動がないということも分かった.
つまり,Long型は別の変数に代入すると別のオブジェクトとして生成されるということになる.
この結果は,類似のInteger型には言える.
だがDouble型とFloat型では,テストメソッドの(3)と(4)でvalueOf()がオブジェクト型を返しているためか,評価はNGとなった.
(逆になぜLong型とInteger型はvalueOf()でTrueになったかが疑問…)
「xxx == Xxx.parseXxx()」や「xxx == Xxx.valueOf()」とする場合の注意点として,整数型か小数型か,ということになる.ということで,以下に等価可能か否かを表にしました.
型 | Xxx.parseXxx() | Xxx.valueOf() |
---|---|---|
Long | 可能 | 可能 |
Integer | 可能 | 可能 |
Double | 可能 | 不可能 |
Float | 可能 | 不可能 |
実は,テストメソッドの(5)はプリミティブ型同士の比較で,(1), (2), (6)は[プリミティブ型]==[オブジェクト型]の等価比較となっている.
そのため,「Trueになっているのではないか?」ということが考えられる.
このままだとズルズルと引きずってしまうので,この疑問は別の機会に検証したいと思います.
一旦お開きということで...
おまけ
オートボクシングの性質も確認したかったので,以下のテストメソッドで実験をしてみた.
引数をObject型にしたメソッドを用意して,いろんな値をメソッドに放り込んでどんなクラスになるかを確認するようなものです.
(検証環境:Java8, JUnit4)
@Test
public void testAutoBoxing() {
// 引数のクラス名を返すだけのクラス
class AutoBoxingTester {
public String get(Object obj) {
return obj.getClass().getName();
}
};
AutoBoxingTester tester = new AutoBoxingTester();
// いろんな値を放り込んでみた
assertEquals(Integer.class.getName(), tester.get(100));
assertEquals(Long.class.getName(), tester.get(100L));
assertEquals(String.class.getName(), tester.get("str"));
assertEquals(Float.class.getName(), tester.get(3.14f));
assertEquals(Double.class.getName(), tester.get(6.0e23));
}