Seltsames Integer-Boxen in Java

114

Ich habe gerade einen ähnlichen Code gesehen:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

Wenn dieser Codeblock ausgeführt wird, wird Folgendes ausgedruckt:

false
true

Ich verstehe, warum das erste ist false: Weil die beiden Objekte getrennte Objekte sind, ==vergleicht das die Referenzen. Aber ich kann nicht herausfinden, warum die zweite Aussage zurückkehrt true. Gibt es eine seltsame Autoboxing-Regel, die aktiviert wird, wenn der Wert einer Ganzzahl in einem bestimmten Bereich liegt? Was ist denn hier los?

Joel
quelle
1
Sieht aus wie ein Betrüger von stackoverflow.com/questions/1514910/…
3
@RC - Nicht ganz ein Betrüger, aber eine ähnliche Situation wird diskutiert. Vielen Dank für den Hinweis.
Joel
2
das ist schrecklich Deshalb habe ich den Sinn dieses ganzen Primitivs nie verstanden, sondern Objekt, sondern beides, aber automatisch verpackt, aber abhängig, aber aaaaaaaargh.
NJZK2
1
@Razib: Das Wort "Autoboxing" ist kein Code, also formatiere es nicht so.
Tom

Antworten:

102

Die trueLeitung wird tatsächlich durch die Sprachspezifikation garantiert. Aus Abschnitt 5.1.7 :

Wenn der Wert p, der eingerahmt wird, wahr, falsch, ein Byte, ein Zeichen im Bereich von \ u0000 bis \ u007f oder eine int oder kurze Zahl zwischen -128 und 127 ist, dann seien r1 und r2 das Ergebnis von zwei beliebigen Boxumwandlungen von p. Es ist immer so, dass r1 == r2.

Die Diskussion geht weiter und schlägt vor, dass Ihre zweite Ausgabezeile zwar garantiert ist, die erste jedoch nicht (siehe den letzten unten zitierten Absatz):

Idealerweise würde das Boxen eines gegebenen Grundwerts p immer eine identische Referenz ergeben. In der Praxis ist dies mit vorhandenen Implementierungstechniken möglicherweise nicht möglich. Die obigen Regeln sind ein pragmatischer Kompromiss. Die letzte Klausel oben verlangt, dass bestimmte gemeinsame Werte immer in nicht unterscheidbare Objekte gepackt werden. Die Implementierung kann diese träge oder eifrig zwischenspeichern.

Für andere Werte erlaubt diese Formulierung keine Annahmen über die Identität der umrahmten Werte seitens des Programmierers. Dies würde das Teilen einiger oder aller dieser Referenzen ermöglichen (aber nicht erfordern).

Dies stellt sicher, dass in den meisten Fällen das Verhalten das gewünschte ist, ohne dass eine unangemessene Leistungseinbuße entsteht, insbesondere bei kleinen Geräten. Weniger speicherbeschränkte Implementierungen können beispielsweise alle Zeichen und Kurzschlüsse sowie Ganzzahlen und Longs im Bereich von -32 KB bis + 32 KB zwischenspeichern.

Jon Skeet
quelle
17
Es kann auch erwähnenswert sein, dass Autoboxing eigentlich nur syntaktischer Zucker ist, um die valueOfMethode der Box-Klasse (wie Integer.valueOf(int)) aufzurufen . Interessant, dass das JLS das genaue Unboxing-Desugaring definiert - unter Verwendung von intValue()et al. - aber nicht das Boxing-Desugaring.
Gustafc
@gustafc gibt es keine andere Möglichkeit zum Entpacken Integerals über die offizielle publicAPI, dh das Aufrufen intValue(). Es gibt jedoch auch andere Möglichkeiten, eine IntegerInstanz für einen intWert abzurufen, z. B. kann ein Compiler Code generieren, der zuvor erstellte IntegerInstanzen beibehält und wiederverwendet .
Holger
31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

Ausgabe:

false
true

Ja, die erste Ausgabe wird zum Vergleichen der Referenz erzeugt. 'a' und 'b' - das sind zwei verschiedene Referenzen. In Punkt 1 werden tatsächlich zwei Referenzen erstellt, die ähnlich sind wie -

Integer a = new Integer(1000);
Integer b = new Integer(1000);

Die zweite Ausgabe wird erzeugt, weil JVMversucht wird, Speicher zu sparen, wenn die Integerin einen Bereich fällt (von -128 bis 127). Bei Punkt 2 wird für 'd' keine neue Referenz vom Typ Integer erstellt. Anstatt ein neues Objekt für die Referenzvariable vom Typ Integer 'd' zu erstellen, wird es nur dem zuvor erstellten Objekt zugewiesen, auf das mit 'c' verwiesen wird. All dies wird von erledigt JVM.

Diese Speicherregeln gelten nicht nur für Integer. Aus Gründen der Speicherersparnis sind zwei Instanzen der folgenden Wrapper-Objekte (während sie durch Boxen erstellt wurden) immer ==, wobei ihre primitiven Werte gleich sind -

  • Boolescher Wert
  • Byte
  • Zeichen von \ u0000 bis \u007f(7f ist 127 in Dezimalzahl)
  • Short und Integer von -128 bis 127
Razib
quelle
2
Longhat auch Cache mit dem gleichen Bereich wie Integer.
Eric Wang
8

Ganzzahlige Objekte in einem bestimmten Bereich (ich denke, vielleicht -128 bis 127) werden zwischengespeichert und wiederverwendet. Ganzzahlen außerhalb dieses Bereichs erhalten jedes Mal ein neues Objekt.

Adam Crume
quelle
1
Dieser Bereich kann mithilfe der java.lang.Integer.IntegerCache.highEigenschaft erweitert werden . Interessant, dass Long diese Option nicht hat.
Aleksandr Kravets
5

Ja, es gibt eine seltsame Autoboxing-Regel, die aktiviert wird, wenn die Werte in einem bestimmten Bereich liegen. Wenn Sie einer Objektvariablen eine Konstante zuweisen, besagt nichts in der Sprachdefinition, dass ein neues Objekt erstellt werden muss . Möglicherweise wird ein vorhandenes Objekt aus dem Cache wiederverwendet.

Tatsächlich speichert die JVM zu diesem Zweck normalerweise einen Cache mit kleinen Ganzzahlen sowie Werte wie Boolean.TRUE und Boolean.FALSE.

Avi
quelle
4

Ich vermute, dass Java einen Cache mit kleinen Ganzzahlen verwaltet, die bereits "eingerahmt" sind, weil sie so häufig vorkommen, und es spart eine Menge Zeit, ein vorhandenes Objekt wiederzuverwenden, als ein neues zu erstellen.

Allgegenwärtig
quelle
4

Das ist ein interessanter Punkt. In dem Buch Effective Java wird empfohlen, immer Gleiches für Ihre eigenen Klassen zu überschreiben. Um die Gleichheit für zwei Objektinstanzen einer Java-Klasse zu überprüfen, verwenden Sie immer die Methode equals.

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

kehrt zurück:

true
true
AmirHd
quelle
@Joel fragte nach einem ganz anderen Thema, nicht nach der Ganzzahlgleichheit, sondern nach dem Laufzeitverhalten von Objekten.
Iliya Kuznetsov
3

In Java funktioniert das Boxen im Bereich zwischen -128 und 127 für eine Ganzzahl. Wenn Sie Zahlen in diesem Bereich verwenden, können Sie diese mit dem Operator == vergleichen. Für Integer-Objekte außerhalb des Bereichs müssen Sie equals verwenden.

Marvin
quelle
3

Die direkte Zuweisung eines int-Literals zu einer Integer-Referenz ist ein Beispiel für das automatische Boxen, bei dem der Literalwert zum Objektkonvertierungscode vom Compiler verarbeitet wird.

Während der Kompilierungsphase konvertiert der Compiler Integer a = 1000, b = 1000;in Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Es ist also die Integer.valueOf()Methode, die uns tatsächlich die Ganzzahlobjekte gibt, und wenn wir uns den Quellcode der Integer.valueOf()Methode ansehen, können wir deutlich sehen, dass die Methode Ganzzahlobjekte im Bereich von -128 bis 127 (einschließlich) zwischenspeichert.

/**
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
 public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

Anstatt neue ganzzahlige Objekte zu erstellen und zurückzugeben, gibt Integer.valueOf()die Methode ganzzahlige Objekte aus dem internen zurück, IntegerCachewenn das übergebene int-Literal größer als -128 und kleiner als 127 ist.

Java speichert diese Ganzzahlobjekte zwischen, da dieser Bereich von Ganzzahlen in der täglichen Programmierung häufig verwendet wird, wodurch indirekt etwas Speicherplatz gespart wird.

Der Cache wird bei der ersten Verwendung initialisiert, wenn die Klasse aufgrund des statischen Blocks in den Speicher geladen wird. Der maximale Bereich des Caches kann über die -XX:AutoBoxCacheMaxJVM-Option gesteuert werden .

Dieses Caching - Verhalten ist nicht anwendbar für Integer nur Objekte, ähnlich wie Integer.IntegerCache haben wir auch ByteCache, ShortCache, LongCache, CharacterCachefür Byte, Short, Long, Characterjeweils.

Sie können mehr über meinen Artikel Java Integer Cache lesen - Warum Integer.valueOf (127) == Integer.valueOf (127) wahr ist .

Naresh Joshi
quelle
0

In Java 5 wurde eine neue Funktion eingeführt, um Speicherplatz zu sparen und die Leistung für die Behandlung von Objekten vom Typ Integer zu verbessern. Ganzzahlige Objekte werden intern zwischengespeichert und über dieselben referenzierten Objekte wiederverwendet.

  1. Dies gilt für Integer-Werte im Bereich zwischen –127 und +127 (Max Integer-Wert).

  2. Dieses Integer-Caching funktioniert nur beim Autoboxing. Ganzzahlige Objekte werden nicht zwischengespeichert, wenn sie mit dem Konstruktor erstellt werden.

Für weitere Details gehen Sie bitte unter Link:

Ganzzahliger Cache im Detail

Rahul Maurya
quelle
0

Wenn wir den Quellcode von Integerobeject überprüfen, finden wir die Quelle der valueOfMethode wie folgt :

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Dies kann erklären, warum IntegerObjekte, die im Bereich von -128 ( Integer.low) bis 127 ( Integer.high) liegen, während des Autoboxing dieselben referenzierten Objekte sind. Und wir können sehen, dass es eine Klasse gibt, IntegerCachedie sich um das IntegerCache-Array kümmert , das eine private statische innere Klasse der IntegerKlasse ist.

Es gibt ein weiteres interessantes Beispiel, das uns helfen kann, diese seltsame Situation zu verstehen:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
L Joey
quelle