2009-10-03 8 views
149

Ich weiß, dass, wenn Sie einen Boxed primitive Integer mit einem konstanten vergleichen wie:Wie zwei Integer in Java richtig vergleichen?

Integer a = 4; 
if (a < 5) 

a automatisch unboxed sein wird und der Vergleich funktioniert.

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

Wird der obige Code zur Überprüfung führen, ob es sich um das gleiche Objekt handelt, oder wird es in diesem Fall automatisch ausgepackt?

Was:

Integer a = 4; 
Integer b = 5; 

if (a < b) 

?

+13

Nun, was passiert, wenn Sie versucht? Was hast du beobachtet? –

+17

@ Bart Kiers: Ein explizites Experiment konnte nur widerlegen, nicht beweisen, dass Unboxing auftritt. Wenn Sie '==' anstelle von 'equals' verwenden, erhalten Sie das korrekte Ergebnis, möglicherweise weil die eingerahmten Zahlen intern gespeichert oder anderweitig wiederverwendet werden (vermutlich als Compiler-Optimierung). Der Grund, diese Frage zu stellen, besteht darin, herauszufinden, was intern geschieht und nicht, was scheinbar geschieht. (Zumindest deshalb bin ich hier.) –

Antwort

30

== wird immer noch Objektgleichheit testen. Es ist jedoch leicht zu täuschen:

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 auf Objekte definiert sind. Beim Vergleich == wird die Objektgleichheit jedoch weiterhin überprüft. Wenn Sie in diesem Fall die Objekte von einem Box-Primitiv initialisieren, wird dasselbe Objekt verwendet (für a und b). Dies ist eine gute Optimierung, da die primitiven Boxklassen unveränderlich sind.

+0

Ich dachte, es wurde die Gleichheit der Objekte getestet. Ich hatte einige seltsame Ergebnisse. Soll ich es durch .equals() ersetzen? Glauben Sie auch, ich sollte die Ungleichheiten so lassen oder anders machen? –

+0

Es gibt einige nicht offensichtliche Randfälle mit Autoboxing. Ich habe meine IDE (Eclipse) so eingestellt, dass alles, was nicht eingerahmt ist, in Rot eingefärbt wird. Dies hat mich bei einigen Gelegenheiten vor Fehlern bewahrt. Wenn Sie zwei Ganzzahlen vergleichen, verwenden Sie .equals, wenn Sie Ihre Ungleichungen verdeutlichen möchten, schreiben Sie die Umwandlung explizit: if ((int) c <(int) d) ...; Sie können auch tun: c.compareTo (d) <0 // === c

+11

Und wenn Sie die Zahl Literale auf '200' ändern, werden beide Tests 'false' drucken. –

210

Nein, == zwischen Integer, Long etc prüft für Referenz Gleichheit - das heißt

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

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

dies prüfen, ob x und y zum gleiche Objekt beziehen anstatt gleich Objekte.

So

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

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

garantiert false zu drucken. Internierung von „kleinen“ autoboxed Werte können zu heikel Ergebnissen führen:

Integer x = 10; 
Integer y = 10; 

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

Dieses true gedruckt wird, auf Grund der Regeln des Boxens (JLS section 5.1.7). Es wird immer noch Referenzgleichheit verwendet, aber die Referenzen sind gleich.

Persönlich würde ich verwenden:

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

oder

if (x.equals(y)) 

Letzteres ist etwas weniger effizient - es ist nicht eine Überlastung für Integer.equals(Integer) so wird es müssen Ausführungszeit Typprüfung tun , während die erste die Tatsache nutzt, dass wir bereits wissen, dass beide Objekte Integer s sind.

Glücklicherweise kennt compareTo über die Typen, so:

if (x.compareTo(y) < 0) 

noch effizient sein sollte. Natürlich ist dieses Gebiet Mikro-Optimierung und Sie sollten den Code, den Sie klarste finden verwenden - nachdem sicherstellen, dass es richtig :) ist

Wie Sie sagen, für jeden Vergleich zwischen einem Wrapper-Typ (Integer, Long usw.) und ein numerisches type (int, long etc) der Wert des Wrappertyps ist ungepackt und der Test wird auf die beteiligten primitiven Werte angewendet.

Dies tritt als Teil der binären numerischen Heraufstufung auf (JLS section 5.6.2). Sehen Sie sich die Dokumentation jedes einzelnen Operators an, um zu sehen, ob sie angewendet wird. ! Zum Beispiel aus der Dokumentation für == und = (JLS 15.21.1):

Wenn die Operanden einer Gleichbehandlungs Operators sind beide numerischen Typ oder ist von numerischem Typ und die anderen umwandelbar ist (§5.1.8) zu numerischen Typ, binäre numerische Förderung ist durchgeführt an den Operanden (§5.6.2).

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

den Typ jedes der Operanden eines numerischen Vergleichsoperator muss einem Typ sein, umwandelbar ist (§5.1 .8) bis ein primitiver numerischer Typ oder ein Fehler bei der Kompilierung auftritt. Binary numerische Förderung wird auf die Operanden (§5.6.2) durchgeführt. Wenn der hochgestufte Typ der Operanden int oder long ist, wird dann der vorzeichenbehaftete ganzzahlige Vergleich durchgeführt; Wenn dieser heraufgestufte Typ float oder double ist, wird der Gleitkomma Vergleich durchgeführt.

Beachten Sie, wie nichts davon als Teil der Situation in Betracht gezogen wird, wo weder Typ ein numerischer Typ ist.

+0

Gibt es einen Grund, warum man "x.compareTo (y) <0" anstatt "x

+0

@MaxNanasy: Nicht, dass ich sofort daran denken kann. –

+1

Ab Java 1.6.27+ gibt es eine Überladung auf equals in der Integer-Klasse, daher sollte es genauso effizient sein wie das Aufrufen von .intValue(). Es vergleicht die Werte als primitives int. – otterslide

4

tl; dr Meiner Meinung nach ist ein unärer + zu verwenden, um das Unboxing auf einer der Operanden zu triggern, wenn für Wertgleichheit überprüft, und einfach die Mathematik Operatoren anderweitig zu nutzen. Begründung folgt:

Es wurde bereits erwähnt, dass == Vergleich für Integer ist Identität Vergleich, die in der Regel nicht, was ein Programmierer wollen, und dass das Ziel ist, Wertvergleich zu tun; noch, ich habe ein wenig getan Wissenschaft darüber, wie dieser Vergleich am effizientesten zu tun, sowohl in Bezug auf Code Kompaktheit, Korrektheit und Geschwindigkeit.

habe ich die üblichen Bündel von Methoden:

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 bekam diesen Code nach der Kompilierung und Dekompilierung:

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 man unschwer erkennen kann, Methode 1 Anrufe Integer.equals() (offensichtlich), Methoden 2-4 Ergebnis in genau der gleiche Code, Auspacken der Werte mittels .intValue() und dann direkt Vergleiche, und Methode 5 löst nur einen Identitätsvergleich, der die falsche Möglichkeit zum Vergleichen von Werten ist.

Da (wie bereits erwähnt beispielsweise JS) equals() verursacht einen Overhead (es hat instanceof und einen ungeprüften Guss zu tun), Methoden 2-4 wird mit der gleichen Geschwindigkeit genau arbeiten, noticingly Verfahren besser als 1, wenn sie in engen verwendet Schleifen, da HotSpot wahrscheinlich nicht die Casts & instanceof optimieren wird.

Es ist ziemlich ähnlich mit anderen Vergleichsoperatoren (zB </>) - sie werden Unboxing auslösen, während compareTo() Verwendung wird nicht - aber diesmal ist der Betrieb sehr optimierbar von HS seit intValue() nur ein Getter-Methode ist (prime Kandidat für die Optimierung).

Meiner Meinung nach, die selten verwendete Version 4 ist die prägnante Art und Weise - jeder gewürzt C/Java-Entwickler weiß, dass unäres Plus in den meisten Fällen gleich int/.intValue() werfen - während es ein wenig WTF sein kann Moment für einige (vor allem diejenigen, die unary plus in ihrem Leben nicht verwendet haben), zeigt es wohl am deutlichsten und am meisten kurz - es zeigt, dass wir einen int Wert von einem der Operanden wollen, den anderen Wert zum Entpacken als Gut. Es ist auch unbestreitbar am ähnlichsten zu dem regulären i1 == i2 Vergleich, der für primitive int Werte verwendet wird.

Meine Stimme geht für i1 == +i2 & i1 > i2 Stil für Integer Objekte, sowohl für die Leistung & Konsistenzgründen. Es macht den Code auch für Primitive portierbar, ohne etwas anderes als die Typdeklaration zu ändern. Mit benannten Methoden scheint mir semantisches Rauschen zu erzeugen, ähnlich dem viel kritisierten Stil.

8

== prüft Referenz Gleichheit, aber beim Schreiben von Code wie:

Integer a = 1; 
Integer b = 1; 

Java intelligent genug ist, um das gleiche unveränderlich für a und b wieder zu verwenden, so dass dies wahr ist: a == b. Neugierig, schrieb ich ein kleines Beispiel zu zeigen, wo Java auf diese Weise stoppt Optimierung:

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 kompilieren und ausführen dies (auf meinem Rechner), erhalte ich:

Done: 128 
+1

tl; dr -1 für Handwaving; http://stackoverflow.com/questions/15052216/how-large-is-the-integer-cache http: // stackoverflow.com/questions/20897020/why-integer-class-caching-werte-im-bereich-128-zu-127 http://stackoverflow.com/questions/3131136/integers-caching-in-java usw. erklären in präzisieren Sie die von Ihnen erwähnte Angelegenheit; es ist besser, die docs (oder lib source) zu lesen, als pseudo-tests mit dem risiko einer hohen lokalität der ergebnisse zu erstellen - nicht nur, dass sie die untergrenze des cache (dh -128 standardmäßig) komplett vergessen haben, nicht nur Sie haben eine einzelne (die max ist 127, nicht 128), – vaxquis

+0

aber Sie haben * vollständig keine Garantie *, um das gleiche Ergebnis auf jedem Rechner zu erhalten - da können Sie einfach die Cache-Größe selbst * erhöhen * YMMV. Die Frage von OP war auch, * wie man zwei Ganzzahlen * richtig vergleicht - Sie * haben es überhaupt nicht beantwortet *. – vaxquis

+0

Ich respektiere deine Meinung und Wahrnehmung hier. Ich denke, wir haben grundlegend andere Ansätze für CS. –

3

Aufruf

if (a == b) 

Wird die meiste Zeit arbeiten, aber es ist nicht garantiert, immer zu funktionieren, also verwenden Sie es nicht.

Die richtige Art und Weise zwei Integer-Klassen für Gleichheit zu vergleichen, vorausgesetzt, sie benannt sind ‚a‘ und ‚b‘ ist zu nennen:

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

Sie auch auf diese Weise verwenden können, die etwas schneller ist.

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

Auf meiner Maschine dauerte 99 Milliarden Operationen 47 Sekunden mit der ersten Methode und 46 Sekunden mit der zweiten Methode. Sie müssten Milliarden von Werten vergleichen, um einen Unterschied zu sehen.

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

Für mehr und weniger als Vergleich, verwenden

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

'if (a == b)' funktioniert nur für kleine Werte und wird die meiste Zeit nicht funktionieren. – Tony

+0

Es funktioniert bis zu 127, da dies der Standard-Integer-Cache von Java ist, der sicherstellt, dass alle Zahlen bis 127 den gleichen Referenzwert haben. Sie können den Cache auf einen höheren Wert als 127 setzen, wenn Sie möchten, aber verwenden Sie nicht ==, um sicher zu sein. – otterslide

-1

diese Methode zwei Integer mit NULL-Prüfung vergleicht, 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 
+0

fügen Sie eine Erklärung zu Ihrer Antwort .. – RamPrakash

+2

Dafür, ich denke, es wäre nur besser, 'Objects.equals (x, y)' Methode zu verwenden, statt Ihre eigene zu rollen. – ryvantage