IT社畜犬くわっちょのはてな

青森の片隅で働く、フルスタックエンジニアに憧れる器用貧乏なIT社畜犬の遠吠え

今更ながら改めて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になったのでなんともしっくりこない。

とこんなことをコードと共にしっくりこないことを呟いたところ以下の指摘をいただく。

実際Longのコードを見てみると確かに

    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を使うように修正。
ラッパークラスがプリミティブ型とおなじような比較をある程度サポートしてくれているとはいえ、安易にそれに頼ってはいけないと改めて気づかされた次第。