Wie vergleiche ich zwei Ganzzahlen in Java richtig?

215

Ich weiß, dass, wenn Sie eine Boxed Primitive Integer mit einer Konstanten wie der folgenden vergleichen:

Integer a = 4;
if (a < 5)

a wird automatisch entpackt und der Vergleich funktioniert.

Was passiert jedoch, wenn Sie zwei Boxen Integersvergleichen und entweder Gleichheit oder weniger als / größer als vergleichen möchten?

Integer a = 4;
Integer b = 5;

if (a == b)

Wird der obige Code dazu führen, dass überprüft wird, ob es sich um dasselbe Objekt handelt, oder wird er in diesem Fall automatisch entpackt?

Wie wäre es mit:

Integer a = 4;
Integer b = 5;

if (a < b)

?

shmosel
quelle
16
Was ist passiert, als du es versucht hast? Was hast du beobachtet?
Bart Kiers
31
@ Bart Kiers: Ein explizites Experiment konnte nur widerlegen, nicht beweisen, dass Unboxing auftritt. Wenn die Verwendung von ==anstelle von equalsdas richtige Ergebnis liefert, kann dies daran liegen, dass die Zahlen in Kästchen interniert oder anderweitig wiederverwendet werden (vermutlich als Compiler-Optimierung). Der Grund, diese Frage zu stellen, besteht darin, herauszufinden, was intern geschieht und nicht, was anscheinend geschieht. (Zumindest bin ich deshalb hier.)
Jim Pivarski
Was ist mit deinem Konto passiert?
96 94

Antworten:

301

Nein, == zwischen Integer, Long usw. prüft die Referenzgleichheit - dh

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

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

Dadurch wird geprüft, ob xund yauf dasselbe Objekt und nicht auf gleiche Objekte verwiesen wird.

So

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

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

wird garantiert gedruckt false. Das Internieren von "kleinen" Autoboxwerten kann zu schwierigen Ergebnissen führen:

Integer x = 10;
Integer y = 10;

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

Dies wird trueaufgrund der Regeln des Boxens gedruckt ( JLS Abschnitt 5.1.7 ). Es ist immer noch Gleichheit Referenz verwendet wird, aber die Referenzen echt sind gleich.

Wenn der Wert p, der eingerahmt wird, ein ganzzahliges Literal vom Typ int zwischen -128 und einschließlich 127 (§3.10.1) oder das boolesche Literal true oder false (§3.10.3) oder ein Zeichenliteral zwischen '\ u0000' und ist '\ u007f' einschließlich (§3.10.4), dann seien a und b die Ergebnisse von zwei beliebigen Boxumwandlungen von p. Es ist immer so, dass a == b.

Persönlich würde ich verwenden:

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

oder

if (x.equals(y))

Wie Sie sagen, für jeden Vergleich zwischen einem Wrapper - Typ ( Integer, Longusw.) und einem numerischen Typen ( int,long usw.) der Wrapper Typwert ist unboxed und der Test auf die Grundwerte beteiligt angewendet wird.

Dies erfolgt im Rahmen der binären numerischen Heraufstufung ( JLS-Abschnitt 5.6.2 ). Sehen Sie sich die Dokumentation jedes einzelnen Bedieners an, um festzustellen, ob sie angewendet wird. Zum Beispiel aus den Dokumenten für ==und !=( JLS 15.21.1 ):

Wenn die Operanden eines Gleichheitsoperators beide vom numerischen Typ sind oder einer vom numerischen Typ und der andere vom numerischen Typ konvertierbar ist (§5.1.8), wird für die Operanden eine binäre numerische Heraufstufung durchgeführt (§5.6.2).

und für <, <=, >und >=( JLS 15.20.1 )

Der Typ jedes Operanden eines numerischen Vergleichsoperators muss ein Typ sein, der in einen primitiven numerischen Typ konvertierbar ist (§5.1.8), da sonst ein Fehler bei der Kompilierung auftritt. An den Operanden wird eine binäre numerische Heraufstufung durchgeführt (§5.6.2). Wenn der heraufgestufte Typ der Operanden int oder long ist, wird ein vorzeichenbehafteter Ganzzahlvergleich durchgeführt. Wenn dieser heraufgestufte Typ float oder double ist, wird ein Gleitkomma-Vergleich durchgeführt.

Beachten Sie, dass nichts davon als Teil der Situation betrachtet wird, in der keiner der Typen ein numerischer Typ ist.

Jon Skeet
quelle
2
Gibt es einen Grund, warum man schreiben möchte x.compareTo(y) < 0anstatt x < y?
Max Nanasy
1
@ MaxNanasy: Nicht, dass ich sofort daran denken könnte.
Jon Skeet
2
Ab Java 1.6.27+ gibt es in der Integer-Klasse eine Überladung von equals, daher sollte dies genauso effizient sein wie das Aufrufen von .intValue (). Es vergleicht die Werte als primitive int.
Otterslide
Wie @otterslide sagte, ist dies in Java 8 nicht mehr erforderlich. Der Vergleich von Integer mit Integer erfolgt standardmäßig standardmäßig.
Axel Prieto
1
@Axel: Das Hinzufügen einer Überladung würde das Verhalten des Operators == jedoch nicht ändern, oder? Ich bin momentan nicht in der Lage zu testen, aber ich wäre sehr überrascht, wenn sich das geändert hätte.
Jon Skeet
44

==testet weiterhin die Objektgleichheit. Es ist jedoch leicht, sich täuschen zu lassen:

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

Ihre Beispiele mit Ungleichungen funktionieren, da sie nicht für Objekte definiert sind. Beim ==Vergleich wird jedoch weiterhin die Objektgleichheit überprüft. In diesem Fall wird beim Initialisieren der Objekte aus einem Boxed-Grundelement dasselbe Objekt verwendet (sowohl für a als auch für b). Dies ist eine gute Optimierung, da die primitiven Boxklassen unveränderlich sind.

Adam Lewis
quelle
Ich dachte, es wäre die Objektgleichheit, die getestet wird. Ich hatte einige seltsame Ergebnisse. Soll ich es durch .equals () ersetzen? Glauben Sie auch, ich sollte die Ungleichheiten so lassen, wie sie sind, oder auch anders?
Es gibt einige nicht offensichtliche Randfälle mit Autoboxing. Ich habe meine IDE (Eclipse) so eingestellt, dass alles, was nicht verpackt ist, rot gefärbt wird. Dies hat mich bei einigen Gelegenheiten vor Fehlern bewahrt. Wenn Sie zwei Ganzzahlen vergleichen, verwenden Sie .equals. Wenn Sie Ihre Ungleichungen deutlich machen möchten, schreiben Sie die Umwandlung explizit ein: if ((int) c <(int) d) ...; Sie können auch Folgendes tun: c.compareTo (d) <0 // === c <d
Adam Lewis
12
Wenn Sie die Anzahl der Literale in ändern 200, werden beide Tests gedruckt false.
Daniel Earwicker
2
... bei den meisten JVM-Implementierungen. Je nach Sprachspezifikation kann das Ergebnis zwischen den Implementierungen variieren.
Daniel Earwicker
4
Ich denke, es ist klarer, diese "Referenzgleichheit" zu nennen - auf diese Weise ist es offensichtlich, was Sie meinen. Normalerweise würde ich unter "Objektgleichheit" "das Ergebnis des equalsAufrufs" verstehen .
Jon Skeet
28

Seit Java 1.7 können Sie Objects.equals verwenden :

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

Gibt true zurück, wenn die Argumente gleich sind, andernfalls false. Wenn beide Argumente null sind, wird true zurückgegeben, und wenn genau ein Argument null ist, wird false zurückgegeben. Andernfalls wird die Gleichheit unter Verwendung der Methode equals des ersten Arguments bestimmt.

Genauso wie
quelle
Dies behandelt Nullen und macht es so einfach. Vielen Dank!
Darren Parker
10

== prüft jedoch auf Referenzgleichheit, wenn Code geschrieben wird wie:

Integer a = 1;
Integer b = 1;

Java ist klug genug, um dasselbe unveränderliche für aund wiederzuverwenden b, also ist dies wahr : a == b. Neugierig schrieb ich ein kleines Beispiel, um zu zeigen, wo Java auf diese Weise aufhört zu optimieren:

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");
    }
}

Wenn ich dies kompiliere und ausführe (auf meinem Computer), erhalte ich:

Done: 128
Cory Kendall
quelle
1
tl; dr -1 zum Handwinken; stackoverflow.com/questions/15052216/… stackoverflow.com/questions/20897020/… stackoverflow.com/questions/3131136/integers-caching-in-java usw. erläutern Sie die von Ihnen erwähnte Angelegenheit ausführlich. Es ist besser, die Dokumente (oder die lib-Quelle) zu lesen, als Pseudotests mit dem Risiko einer hohen Lokalität der Ergebnisse zu erstellen. Sie haben nicht nur die Untergrenze des Caches (dh standardmäßig -128) vollständig vergessen, nicht nur Sie haben off-by-one (das
Sie können jedoch nicht garantieren , dass auf jedem Computer das gleiche Ergebnis erzielt wird - da Sie die Cache-Größe einfach selbst erhöhen können, YMMV. Die Frage von OP war auch, wie zwei Ganzzahlen richtig verglichen werden können - Sie haben sie überhaupt nicht beantwortet .
Ich respektiere Ihre Meinung und Wahrnehmung hier. Ich denke, wir haben nur grundlegend unterschiedliche Ansätze für CS.
Cory Kendall
1
Es geht nicht um Meinung oder Wahrnehmung - es geht um die Fakten , die Sie aufrichtig übersehen haben. Ein Pseudo-Test, der nichts beweist, ohne feste Daten (Dokumente, Quelle usw.) und ohne Beantwortung der Fragen von OP, verdient es nicht, weder als gute Fragen und Antworten noch als CS bezeichnet zu werden. In Bezug auf "andere Herangehensweise" ist CS per Definition eine Wissenschaft ; Was du in der Wissenschaft getan hast, ist nicht ; Es ist eine irreführende Trivia (oder es wäre ein faszinierender Kommentar , wenn es richtig ausgedrückt wird) - wenn Sie möchten, dass es Wissenschaft ist , korrigieren Sie die grundlegenden Fehler in Ihrer Antwort oder entlarven Sie sie vernünftig , da dies so funktioniert.
Klar, dann werde ich versuchen, die Mängel zu beheben. Ich habe die Untergrenze nicht vergessen, ich fand sie nicht interessant und entschied mich, sie nicht aufzunehmen. Ich glaube nicht, dass ich durch einen Fehler einen Fehler habe. Ich habe angegeben, wie Java (das ich in meiner Situation auf meinem Computer geklärt habe) aufgehört hat, dies zu optimieren. Es liegt bei 128. Wenn ich den Maximalwert angegeben hätte, den es hat Dies für, als Sie Recht haben, wäre die Antwort 127 gewesen.
Cory Kendall
8

tl; dr meine meinung ist es, eine unäre zu verwenden+ die Unboxing auf einer der Operanden zu triggern , wenn für Wertgleichheit überprüft, und nur die Operatoren Mathematik anderweitig zu nutzen. Begründung folgt:

Es wurde bereits erwähnt, dass der ==Vergleich für einen IntegerIdentitätsvergleich ist, was normalerweise nicht das ist, was ein Programmierer möchte, und dass das Ziel darin besteht, einen Wertevergleich durchzuführen. noch, ich habe ein wenig getan Wissenschaft darüber , wie dieser Vergleich zu tun , am effizientesten, sowohl in der Bezeichnung des Code Kompaktheit, Korrektheit und Geschwindigkeit.

Ich habe die üblichen Methoden angewendet:

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;
}

und habe diesen Code nach dem Kompilieren und Dekompilieren erhalten:

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;
    }
}

Wie Sie leicht sehen können, rufen Methode 1 Integer.equals()(offensichtlich) auf, Methoden 2-4 führen zu genau demselben Code und entpacken die Werte mit.intValue() und dann direktem Vergleich entpackt werden, und Methode 5 löst lediglich einen Identitätsvergleich aus, was der falsche Weg ist Werte vergleichen.

Da (wie bereits von z. B. JS erwähnt) equals()ein Overhead entsteht (dies muss geschehen instanceofund eine ungeprüfte Umwandlung), arbeiten die Methoden 2 bis 4 mit genau der gleichen Geschwindigkeit, die in engen Schleifen deutlich besser ist als Methode 1, da HotSpot dies nicht ist wahrscheinlich die Casts & zu optimieren instanceof.

Ähnlich verhält es sich mit anderen Vergleichsoperatoren (z. B. </ >) - sie lösen Unboxing aus, während sie dies compareTo()nicht tun -, aber diesmal ist die Operation von HS seitdem in hohem Maße optimierbarintValue() es sich nur um eine Getter-Methode handelt (Hauptkandidat für eine Optimierung).

Meiner Meinung nach ist die selten verwendete Version 4 die prägnanteste Methode - jeder erfahrene C / Java-Entwickler weiß, dass unäres Plus in den meisten Fällen gleichbedeutend mit int/ ist .intValue()-, während es für einige (meistens für diejenigen, die dies nicht getan haben) ein kleiner WTF- Moment sein kann (verwenden Sie unary plus in ihrem Leben nicht), es zeigt wohl die Absicht am klarsten und am knappsten - es zeigt, dass wir einen intWert für einen der Operanden wollen, wodurch der andere Wert ebenfalls zum Entpacken gezwungen wird. Es ist auch dem regulären i1 == i2Vergleich, der für primitive intWerte verwendet wird, unbestreitbar am ähnlichsten .

Meine Stimme gilt für i1 == +i2& i1 > i2Stil für IntegerObjekte, sowohl aus Gründen der Leistung als auch aus Gründen der Konsistenz. Außerdem kann der Code auf Grundelemente portiert werden, ohne dass etwas anderes als die Typdeklaration geändert werden muss. Die Verwendung benannter Methoden scheint mir semantisches Rauschen einzuführen, ähnlich dem viel kritisierten bigInt.add(10).multiply(-3)Stil.


quelle
Können Sie erklären, was das + in Methode 4 bedeutet? Ich habe versucht, es zu googeln, aber ich habe nur die normalen Verwendungen dieses Symbols erhalten (Addition, Verkettung).
Alex Li
1
@AlexLi bedeutet genau das, was ich geschrieben habe - unary +(unäres Plus), siehe z. B. stackoverflow.com/questions/2624410/…
8

Berufung

if (a == b)

Funktioniert die meiste Zeit, es ist jedoch nicht garantiert, dass es immer funktioniert. Verwenden Sie es daher nicht.

Der beste Weg, zwei Integer-Klassen auf Gleichheit zu vergleichen, vorausgesetzt, sie heißen 'a' und 'b', besteht darin, Folgendes aufzurufen:

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

Sie können auch diesen Weg verwenden, der etwas schneller ist.

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

Auf meinem Computer dauerten 99 Milliarden Vorgänge mit der ersten Methode 47 Sekunden und mit der zweiten Methode 46 Sekunden. Sie müssten Milliarden von Werten vergleichen, um einen Unterschied festzustellen.

Beachten Sie, dass 'a' möglicherweise null ist, da es sich um ein Objekt handelt. Ein Vergleich auf diese Weise führt nicht zu einer Nullzeigerausnahme.

Verwenden Sie zum Vergleichen von größer und kleiner als

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");
}
Otterslide
quelle
1
if (a==b)funktioniert nur für kleine Werte und funktioniert meistens nicht.
Tony
Es funktioniert bis zu 127, da dies Javas Standard-Integer-Cache ist, der sicherstellt, dass alle Zahlen bis zu 127 denselben Referenzwert haben. Sie können den Cache so einstellen, dass er höher als 127 ist, wenn Sie möchten, aber verwenden Sie aus Sicherheitsgründen nicht ==.
Otterslide
2

Wir sollten immer die Methode equals () zum Vergleich für zwei ganze Zahlen wählen. Dies ist die empfohlene Vorgehensweise.

Wenn wir zwei Ganzzahlen mit == vergleichen, funktioniert dies aufgrund der internen Optimierung von JVM für einen bestimmten Bereich von Ganzzahlwerten (Ganzzahl von -128 bis 127).

Bitte sehen Sie Beispiele:

Fall 1:

Ganzzahl a = 100; Ganzzahl b = 100;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Im obigen Fall verwendet JVM den Wert von a und b aus dem zwischengespeicherten Pool und gibt dieselbe Objektinstanz (daher Speicheradresse) des ganzzahligen Objekts zurück, und wir erhalten beide gleich. Es ist eine Optimierung, die JVM für bestimmte Bereichswerte durchführt.

Fall 2: In diesem Fall sind a und b nicht gleich, da sie nicht im Bereich von -128 bis 127 liegen.

Ganzzahl a = 220; Ganzzahl b = 220;

if (a == b) {
    System.out.println("a and b are equal");
} else {
   System.out.println("a and b are not equal");
}

Richtige Weg:

Integer a = 200;             
Integer b = 200;  
System.out.println("a == b? " + a.equals(b)); // true

Ich hoffe das hilft.

Siyaram Malav
quelle
1

In meinem Fall musste ich zwei Integers für die Gleichheit vergleichen, wo beide sein konnten null. Ähnliches Thema gesucht, nichts Elegantes dafür gefunden. Kam mit einem einfachen Dienstprogramm Funktionen.

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);
}
JackHammer
quelle
-1

Weil die Vergleichsmethode basierend auf dem Typ int (x == y) oder der Klasse Integer (x.equals (y)) mit dem rechten Operator durchgeführt werden muss

public class Example {

    public static void main(String[] args) {
     int[] arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<arr.length-1; j++)
            if((arr[j-1]!=arr[j]) && (arr[j]!=arr[j+1])) 
                System.out.println("int>"+arr[j]);


    Integer[] I_arr = {-32735, -32735, -32700, -32645, -32645, -32560, -32560};

        for(int j=1; j<I_arr.length-1; j++)
            if((!I_arr[j-1].equals(I_arr[j])) && (!I_arr[j].equals(I_arr[j+1]))) 
                System.out.println("Interger>"+I_arr[j]);
    }
}
Chronoslog
quelle
-2

Diese Methode vergleicht zwei Ganzzahlen mit der Nullprüfung, siehe Tests

public static boolean compare(Integer int1, Integer int2) {
    if(int1!=null) {
        return int1.equals(int2);
    } else {
        return int2==null;
    }
    //inline version:
    //return (int1!=null) ? int1.equals(int2) : int2==null;
}

//results:
System.out.println(compare(1,1));           //true
System.out.println(compare(0,1));           //false
System.out.println(compare(1,0));           //false
System.out.println(compare(null,0));        //false
System.out.println(compare(0,null));        //false
System.out.println(compare(null,null));     //true
Alex Torson
quelle
4
Aus diesem Grund denke ich, dass es einfach besser wäre, eine Objects.equals(x,y)Methode zu verwenden, als eine eigene zu rollen.
Ryvantage