web-dev-qa-db-ja.com

Javaで2つの整数を正しく比較する方法

ボックス化されたプリミティブ整数を次のような定数と比較するとわかります。

Integer a = 4;
if (a < 5)

aは自動的にボックスから外され、比較が機能します。

しかし、2つのボックスで囲まれたIntegersを比較していて、等しいか、またはより小さい/より大きいかを比較したい場合はどうなりますか?

Integer a = 4;
Integer b = 5;

if (a == b)

上記のコードは、それらが同じオブジェクトであるかどうかを確認することになりますか、それともその場合は自動アンボックスになりますか?

どうですか?

Integer a = 4;
Integer b = 5;

if (a < b)

185
Samatha84

いいえ、== Integer、Longなどの間で参照等価 - をチェックします。

Integer x = ...;
Integer y = ...;

System.out.println(x == y);

これはxyequalオブジェクトではなく同じオブジェクトを参照しているかどうかをチェックします。

そう

Integer x = new Integer(10);
Integer y = new Integer(10);

System.out.println(x == y);

falseの印刷が保証されています。 「小さい」オートボクシングされた値を相互に組み合わせると、トリッキーな結果になる可能性があります。

Integer x = 10;
Integer y = 10;

System.out.println(x == y);

ボクシングの規則( JLS section 5.1.7 )により、これはtrueを表示します。まだ参照等価性が使われていますが、参照は真にare equalです。

個人的には

if (x.intValue() == y.intValue())

または

if (x.equals(y))

後者はやや効率が悪いです - Integer.equals(Integer)のオーバーロードはないので実行時の型チェックをしなければなりませんが、最初のものは両方のオブジェクトがIntegersであることをすでに知っているという事実を使います。

幸い、compareToは型について知っているので、

if (x.compareTo(y) < 0)

まだ効率的なはずです。もちろん、これはマイクロ最適化の領域であり、最もわかりやすいコードを使用してください - 正しいことを確認した後:)

お分かりのように、ラッパー型(IntegerLongなど)と数値型(intlongなど)の比較では、ラッパー型の値はnboxedとなり、関係するプリミティブ値にテストが適用されます。 。

これは、2進数値プロモーション( JLSセクション5.6.2 )の一部として発生します。それが適用されるかどうか見るためにそれぞれの個々のオペレータのドキュメンテーションを見てください。たとえば、==および!=のドキュメント( JLS 15.21.1 )から、

等値演算子のオペランドが両方とも数値型であるか、または一方が数値型でもう一方が数値型に変換可能である場合(5.1.8)、2進数値昇格はオペランドに対して実行されます(5.6.2)。

<<=>および>=の場合( JLS 15.20.1

数値比較演算子の各オペランドの型は、基本数値型に変換可能な型(5.1.8)でなければなりません。そうしないと、コンパイル時エラーが発生します。 2進数値昇格はオペランドに対して実行されます(5.6.2)。昇格されたオペランドの型がintまたはlongの場合は、符号付き整数比較が実行されます。このプロモート型がfloatまたはdoubleの場合、浮動小数点比較が実行されます。

どちらでもない typeが数値型である状況の一部として、これらのどれも考慮されないことに注意してください。

259
Jon Skeet

==はまだオブジェクトの同等性をテストします。だまされるのは簡単です、しかし:

Integer a = 10;
Integer b = 10;

System.out.println(a == b); //prints true

Integer c = new Integer(10);
Integer d = new Integer(10);

System.out.println(c == d); //prints false

不等式を含むあなたの例はオブジェクト上で定義されていないのでうまくいくでしょう。ただし、==比較では、オブジェクトの等価性はチェックされます。この場合、ボックス化プリミティブからオブジェクトを初期化すると、同じオブジェクトが使用されます(aとbの両方)。プリミティブボックスクラスは不変であるため、これは問題ありません。

41
Adam Lewis

Java 1.7以降、 Objects.equals を使用できます。

Java.util.Objects.equals(oneInteger, anotherInteger);

引数が互いに等しい場合はtrueを返し、そうでない場合はfalseを返します。したがって、両方の引数がnullの場合はtrueが返され、一方の引数がnullの場合はfalseが返されます。それ以外の場合、等価は最初の引数のequalsメソッドを使用して決定されます。

14
Justas

==は参照の等価性をチェックしますが、次のようなコードを書くときは:

Integer a = 1;
Integer b = 1;

Javaはabに同じ不変変数を再利用するのに十分賢いので、これは本当です:a == b。興味深いことに、私はJavaがこのように最適化を停止するところを示すために小さな例を書きました:

public class BoxingLol {
    public static void main(String[] args) {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            Integer a = i;
            Integer b = i;
            if (a != b) {
                System.out.println("Done: " + i);
                System.exit(0);
            }
        }
        System.out.println("Done, all values equal");
    }
}

これを(私のマシンで)コンパイルして実行すると、次のようになります。

Done: 128
10
Cory Kendall

呼び出し

if (a == b)

ほとんどの場合動作しますが、常に動作するとは限らないので、使用しないでください。

2つのIntegerクラスが等しいかどうかを比較する最も適切な方法は、それらが 'a'と 'b'という名前であると仮定した場合、次のように呼び出すことです。

if(a != null && a.equals(b)) {
  System.out.println("They are equal");
}

この方法を使うこともできますが、これは少し速いです。

   if(a != null && b != null && (a.intValue() == b.intValue())) {
      System.out.println("They are equal");
    } 

私のマシンでは、990億回の操作で最初の方法で47秒、2番目の方法で46秒かかりました。違いを見るには、何十億もの値を比較する必要があります。

'a'はObjectなのでnullでも構いません。この方法で比較しても、NULLポインタ例外は発生しません。

より大きいと小さいを比較するには、

if (a != null && b!=null) {
    int compareValue = a.compareTo(b);
    if (compareValue > 0) {
        System.out.println("a is greater than b");
    } else if (compareValue < 0) {
        System.out.println("b is greater than a");
    } else {
            System.out.println("a and b are equal");
    }
} else {
    System.out.println("a or b is null, cannot compare");
}
8
otterslide

tl; dr私の意見では、値の等価性をチェックするときに、オペランドの1つでunboxingをトリガーするために単項+を使用し、そうでない場合は単に数学演算子を使用します。根拠は次のとおりです。

Integer==比較は、通常はプログラマーが望むものではないID比較であり、その目的は値の比較を行うことであることは既に述べました。それでも、コードのコンパクトさ、正確さ、速度の両方の面で、その比較を最も効率的に行う方法について、少しscienceを行いました。

私は通常の方法の束を使用しました:

public boolean method1() {
    Integer i1 = 7, i2 = 5;
    return i1.equals( i2 );
}

public boolean method2() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2.intValue();
}

public boolean method3() {
    Integer i1 = 7, i2 = 5;
    return i1.intValue() == i2;
}

public boolean method4() {
    Integer i1 = 7, i2 = 5;
    return i1 == +i2;
}

public boolean method5() { // obviously not what we want..
    Integer i1 = 7, i2 = 5;
    return i1 == i2;
}

コンパイルと逆コンパイル後にこのコードを取得しました:

public boolean method1() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    return var1.equals( var2 );
}

public boolean method2() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method3() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method4() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2.intValue() == var1.intValue() ) {
        return true;
    } else {
        return false;
    }
}

public boolean method5() {
    Integer var1 = Integer.valueOf( 7 );
    Integer var2 = Integer.valueOf( 5 );

    if ( var2 == var1 ) {
        return true;
    } else {
        return false;
    }
}

簡単にわかるように、メソッド1はInteger.equals()を呼び出し(明らかに)、メソッド2から4はとまったく同じコードになり、値をアンラップします.intValue()を使用して、それらを直接比較します。メソッド5は、値を比較する誤った方法であるID比較をトリガーします。

(たとえばJSで既に述べたように)equals()はオーバーヘッド(instanceofと未チェックのキャストを行う必要があります)が発生するため、メソッド2〜4はメソッド1よりもまったく同じ速度で動作します。 HotSpotはキャストとinstanceofを最適化する可能性が低いため、タイトループで使用する場合。

他の比較演算子(例:</>)と非常によく似ています-compareTo()を使用するとアンボックス化がトリガーされませんが、今回はHSによって操作が非常に最適化されますintValue()は単なるゲッターメソッドです(最適化されることの第一候補です)。

私の意見では、めったに使用されないバージョン4が最も簡潔な方法です。すべてのベテランC/Java開発者は、単項プラスがほとんどの場合int/.intValue()へのキャストに等しいことを知っています。少しWTFしばらくの間(主に生涯で単項プラスを使用しなかった人)、ほぼ間違いなく最も簡潔に意図を示しています-オペランドの1つのint値が必要であることを示し、他の値も強制的にunboxにします。また、プリミティブint値に使用される通常のi1 == i2比較とほぼ間違いなく類似しています。

パフォーマンスと一貫性の両方の理由から、Integerオブジェクトのi1 == +i2スタイルとi1 > i2スタイルに投票します。また、型宣言以外を変更せずに、コードをプリミティブに移植可能にします。名前付きメソッドを使用することは、非常に批判されているbigInt.add(10).multiply(-3)スタイルと同様に、セマンティックノイズを導入するように思われます。

7
vaxquis

私の場合、2つのIntegerが等しいかどうかを比較する必要がありました。両方ともnullである可能性があります。似たようなトピックを検索しましたが、これについてエレガントなものは見つかりませんでした。簡単なユーティリティ機能を思いついた。

public static boolean integersEqual(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return true;
    }
    if (i1 == null && i2 != null) {
        return false;
    }
    if (i1 != null && i2 == null) {
        return false;
    }
    return i1.intValue() == i2.intValue();
}

//considering null is less than not-null
public static int integersCompare(Integer i1, Integer i2) {
    if (i1 == null && i2 == null) {
        return 0;
    }
    if (i1 == null && i2 != null) {
        return -1;
    }
    return i1.compareTo(i2);
}
1
JackHammer