今更ながら改めてJavaのLongの比較を試してみた。
ソースレビューをしていた時にふと気づいたこと
Long a = 1L; Long b = 1L;
があったとして、このLong値が一致するかどうかというのを
if (a == b) {}; // 1.
if (a.equals(b)) {}; // 2.
と比較方法が書く人によってバラバラなことに気付く。
まあ、ラッパークラスなので2.の方がいいというのはいいんだが、1.でも問題なく動いていたりするのでどうにも指摘しにくい。
というより自分自身Longの場合に上記の違いをちゃんと学んどこうと思ったのでちょっと試してみた。
まず作ったのはこんなコード
public class LongTest {
public static void main(String[] args) {
Long a = 1L;
Long b = Long.valueOf("1");
// a と b の比較
if (a == b) {
System.out.println("[a == b] true");
} else {
System.out.println("[a == b] false");
}
if (a.equals(b)) {
System.out.println("[a.equals(b)] true");
} else {
System.out.println("[a.equals(b)] false");
}
}
}
で実行結果
[a == b] true [a.equals(b)] true
とどっちもtrueになったのでなんともしっくりこない。
とこんなことをコードと共にしっくりこないことを呟いたところ以下の指摘をいただく。
@kuwaccho0711 興味深かったのでソース見てみました。
— ムチャ@うつ病SE(仕事探してます!) (@mutoj_rdm821) 2016年12月16日
バイトコード見ると生成時はどちらもLong#valueOfに置き換わっています。
内部クラスにLongCacheというのがいて-128~127のLongインスタンスがあらかじめ生成されているようです。→
実際Longのコードを見てみると確かに@kuwaccho0711 valueOfはこのキャッシュを見るので同じインスタンスになります。
— ムチャ@うつ病SE(仕事探してます!) (@mutoj_rdm821) 2016年12月16日
片方をnewにすると、キャッシュを見ないので==はfalseになります。
以上クソリプでした|彡サッ
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
とある。なるほどな。
.longValue()でプリミティブ型に変換して比較する、という指摘もいただいたので2種類試してみる。
まず第1弾。値を1でやってみる。
public class LongTest {
@SuppressWarnings("unused")
public static void main(String[] args) {
Long a = 1L;
Long b = Long.valueOf("1");
Long c = new Long(1L);
Long d = null;
// a と b の比較
if (a == b) {
System.out.println("[a == b] true");
} else {
System.out.println("[a == b] false");
}
if (a.equals(b)) {
System.out.println("[a.equals(b)] true");
} else {
System.out.println("[a.equals(b)] false");
}
// a と c の比較
if (a == c) {
System.out.println("[a == c] true");
} else {
System.out.println("[a == c] false");
}
if (a.equals(c)) {
System.out.println("[a.equals(c)] true");
} else {
System.out.println("[a.equals(c)] false");
}
// b と c の比較
if (b == c) {
System.out.println("[b == c] true");
} else {
System.out.println("[b == c] false");
}
if (b.equals(c)) {
System.out.println("[b.equals(c)] true");
} else {
System.out.println("[b.equals(c)] false");
}
// .longValue() をつける
if (a.longValue() == c.longValue()) {
System.out.println("[a.longValue() == c.longValue()] true");
} else {
System.out.println("[a.longValue() == c.longValue()] false");
}
if (b.longValue() == c.longValue()) {
System.out.println("[b.longValue() == c.longValue()] true");
} else {
System.out.println("[b.longValue() == c.longValue()] false");
}
// a と null の比較
if (a == d) {
System.out.println("[a == d] true");
} else {
System.out.println("[a == d] false");
}
if (a.equals(d)) {
System.out.println("[a.equals(d)] true");
} else {
System.out.println("[a.equals(d)] false");
}
}
}
で実行結果
[a == b] true [a.equals(b)] true [a == c] false [a.equals(c)] true [b == c] false [b.equals(c)] true [a.longValue() == c.longValue()] true [b.longValue() == c.longValue()] true [a == d] false [a.equals(d)] false
なるほど確かにnewするとfalseになるな。
で第2弾。値を128でやってみる。
public class LongTest2 {
public static void main(String[] args) {
Long a = 128L;
Long b = Long.valueOf("128");
Long c = new Long(128L);
// a と b の比較
if (a == b) {
System.out.println("[a == b] true");
} else {
System.out.println("[a == b] false");
}
if (a.equals(b)) {
System.out.println("[a.equals(b)] true");
} else {
System.out.println("[a.equals(b)] false");
}
// a と c の比較
if (a == c) {
System.out.println("[a == c] true");
} else {
System.out.println("[a == c] false");
}
if (a.equals(c)) {
System.out.println("[a.equals(c)] true");
} else {
System.out.println("[a.equals(c)] false");
}
// b と c の比較
if (b == c) {
System.out.println("[b == c] true");
} else {
System.out.println("[b == c] false");
}
if (b.equals(c)) {
System.out.println("[b.equals(c)] true");
} else {
System.out.println("[b.equals(c)] false");
}
// .longValue() をつける
if (a.longValue() == c.longValue()) {
System.out.println("[a.longValue() == c.longValue()] true");
} else {
System.out.println("[a.longValue() == c.longValue()] false");
}
if (b.longValue() == c.longValue()) {
System.out.println("[b.longValue() == c.longValue()] true");
} else {
System.out.println("[b.longValue() == c.longValue()] false");
}
}
}
で実行結果
[a == b] false [a.equals(b)] true [a == c] false [a.equals(c)] true [b == c] false [b.equals(c)] true [a.longValue() == c.longValue()] true [b.longValue() == c.longValue()] true
なるほどたしかにnewしなくても == も通らなくなったね。
これを踏まえ処理を見直してみたところ、今回の場合==でも問題が発覚しなかったのは、Longで型を指定しているにも関わらず値が100以下がほとんどだったためという事もわかった。
まあ、バグの温床になってしまうのでequalsを使うように修正。
ラッパークラスがプリミティブ型とおなじような比較をある程度サポートしてくれているとはいえ、安易にそれに頼ってはいけないと改めて気づかされた次第。