Java Language
Autoboxing
Suche…
Einführung
Autoboxing ist die automatische Konvertierung, die der Java-Compiler zwischen primitiven Typen und den entsprechenden Objekt-Wrapper-Klassen vornimmt. Beispiel: Konvertierung von int -> Integer, double -> Double ... Wenn die Konvertierung anders verläuft, wird dies als Unboxing bezeichnet. In der Regel wird dies in Sammlungen verwendet, die nur Objekte enthalten können, wobei primitive Boxtypen erforderlich sind, bevor sie in der Sammlung festgelegt werden.
Bemerkungen
Autoboxing kann bei häufiger Verwendung in Ihrem Code zu Leistungsproblemen führen.
Int und Integer austauschbar verwenden
Wenn Sie generische Typen mit Dienstprogrammklassen verwenden, stellen Sie oft fest, dass Nummerntypen nicht sehr hilfreich sind, wenn sie als Objekttypen angegeben werden, da sie nicht ihren primitiven Gegenstücken entsprechen.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Glücklicherweise können Ausdrücke, die als int
ausgewertet werden, anstelle einer Integer
wenn sie benötigt wird.
for (int i = 0; i < 10; i++)
ints.add(i);
Die ints.add(i);
Aussage ist äquivalent zu:
ints.add(Integer.valueOf(i));
Und behält Eigenschaften von Integer#valueOf
z. B. dass die gleichen Integer
Objekte von der JVM zwischengespeichert werden, wenn sie sich innerhalb des Zahlen-Caching-Bereichs befinden.
Das gilt auch für:
-
byte
undByte
-
short
undShort
-
float
undFloat
-
double
undDouble
-
long
undLong
-
char
undCharacter
-
boolean
undBoolean
In mehrdeutigen Situationen ist jedoch Vorsicht geboten. Betrachten Sie den folgenden Code:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
Die java.util.List
Schnittstelle enthält sowohl ein remove(int index)
( List
Interface - Methode) und remove(Object o)
(Methode von vererbten java.util.Collection
). In diesem Fall findet kein Boxing statt und remove(int index)
wird aufgerufen.
Ein weiteres Beispiel für ein merkwürdiges Verhalten von Java-Code, das durch die automatische Integierung von Autoboxen mit Werten im Bereich von -128
bis 127
:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b); // true
System.out.println(c <= d); // true
System.out.println(c >= d); // true
System.out.println(c == d); // false
Dies geschieht, weil der Operator >=
implizit intValue()
das int
zurückgibt, während ==
die Referenzen und nicht die int
Werte vergleicht.
Standardmäßig speichert Java Werte im Bereich [-128, 127]
, sodass der Operator ==
funktioniert, da die Integers
Werte in diesem Bereich auf dieselben Objekte verweisen, wenn ihre Werte gleich sind. Der maximale Wert des zwischengespeicherten Bereichs kann mit der JVM-Option -XX:AutoBoxCacheMax
definiert werden. Wenn Sie das Programm mit -XX:AutoBoxCacheMax=1000
, wird der folgende Code also true
-XX:AutoBoxCacheMax=1000
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Boolesche Anweisung in if verwenden
Aufgrund des automatischen Unboxing kann ein Boolean
in einer if
Anweisung verwendet werden:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Das funktioniert auch für while
, do while
und die Bedingung in den for
Anweisungen.
Wenn der Boolean
NullPointerException
null
, wird eine NullPointerException
in die Konvertierung geworfen.
Auto-Unboxing kann zu NullPointerException führen
Dieser Code kompiliert:
Integer arg = null;
int x = arg;
Es stürzt jedoch zur Laufzeit mit einer java.lang.NullPointerException
in der zweiten Zeile ab.
Das Problem ist, dass ein primitives int
keinen null
kann.
Dies ist ein minimalistisches Beispiel, aber in der Praxis manifestiert es sich oft in komplexeren Formen. Die NullPointerException
ist nicht sehr intuitiv und hilft oft bei der Suche nach solchen Fehlern.
Stellen Sie sicher, dass Autoboxing und Auto-Unboxing mit Vorsicht ausgeführt werden, und stellen Sie sicher, dass nicht gepackte Werte zur Laufzeit keine null
haben.
Speicher- und Rechenaufwand für Autoboxing
Autoboxing kann einen erheblichen Speicheraufwand verursachen. Zum Beispiel:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
verbraucht normalerweise eine beträchtliche Menge an Speicher (etwa 60 KB für 6 KB der tatsächlichen Daten).
Darüber hinaus erfordern Boxed Integer normalerweise zusätzliche Roundtrips im Speicher, wodurch die CPU-Caches weniger effektiv werden. In obigem Beispiel ist der Speicher, auf den zugegriffen wird, auf fünf verschiedene Speicherorte verteilt, die sich in ganz unterschiedlichen Speicherbereichen befinden können: 1. das HashMap
Objekt, 2. das Map-Objekt Entry[] table
, 3. das Entry
Objekt, 4. das entrys key
(Box der primitiven Schlüssel), 5. das entrys value
Objekt (Box den Grundwertes).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
Das Lesen von boxed
erfordert zwei Speicherzugriffe, wobei nur einer auf das primitive
zugreifen kann.
Beim Abrufen von Daten aus dieser Karte der scheinbar unschuldige Code
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
ist äquivalent zu:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Normalerweise verursacht der obige Code die Erstellung und Garbage Collection eines Integer
Objekts für jede Map#get(Integer)
-Operation. (Weitere Informationen finden Sie im Hinweis unten.)
Um diesen Aufwand zu reduzieren, bieten mehrere Bibliotheken optimierte Sammlungen für primitive Typen, für die kein Boxen erforderlich ist. Zusätzlich zum Vermeiden des Box-Overheads erfordert diese Sammlung etwa viermal weniger Speicher pro Eintrag. Java Hotspot kann zwar das Autoboxing durch Arbeiten mit Objekten auf dem Stapel statt mit dem Heap optimieren, es ist jedoch nicht möglich, den Speicheraufwand und die daraus resultierende Speicherumleitung zu optimieren.
Java 8-Streams verfügen außerdem über optimierte Schnittstellen für primitive Datentypen, z. B. IntStream
, für die kein Boxen erforderlich ist.
Hinweis: Bei einer typischen Java-Laufzeitumgebung wird ein einfacher Cache aus Integer
und anderen primitiven Wrapper-Objekten verwaltet, der von den Factory-Methoden von valueOf
und von Autoboxing verwendet wird. Für Integer
liegt der Standardbereich dieses Caches zwischen -128 und +127. Einige JVMs bieten eine JVM-Befehlszeilenoption zum Ändern der Cachegröße / des Cache-Bereichs.
Verschiedene Fälle, wenn Integer und Int austauschbar verwendet werden können
Fall 1: Bei Verwendung anstelle von Methodenargumenten.
Wenn für eine Methode ein Objekt der Wrapper-Klasse als Argument erforderlich ist, kann dem Argument eine Variable des jeweiligen primitiven Typs übergeben werden und umgekehrt.
Beispiel:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Fall 2: Beim Übergeben von Rückgabewerten:
Wenn eine Methode eine primitive Typvariable zurückgibt, kann ein Objekt der entsprechenden Wrapper-Klasse austauschbar als Rückgabewert und umgekehrt übergeben werden.
Beispiel:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Fall 3: Während der Durchführung von Operationen.
Bei jeder Ausführung von Operationen mit Zahlen können die primitive Typvariable und das Objekt der jeweiligen Wrapper-Klasse austauschbar verwendet werden.
int i=5;
Integer j=new Integer(7);
int k=i+j;//Is a valid statement
Integer m=i+j;//Is also a valid statement
Fallstricke : Denken Sie daran, ein Objekt der Wrapper-Klasse zu initialisieren oder einem Wert zuzuweisen.
Wenn Sie das Wrapper-Klassenobjekt und die primitive Variable austauschbar verwenden, vergessen Sie niemals, das Wrapper-Klassenobjekt zu initialisieren oder ihm einen Wert zuzuweisen. Andernfalls kann es zur Laufzeit zu einer Nullzeiger-Ausnahme kommen.
Beispiel:
public class Test{
Integer i;
int j;
public void met()
{j=i;//Null pointer exception
SOP(j);
SOP(i);}
public static void main(String[] args)
{Test t=new Test();
t.go();//Null pointer exception
}
Im obigen Beispiel ist der Wert des Objekts nicht zugewiesen und nicht initialisiert, und daher wird das Programm zur Laufzeit in einer Nullzeiger-Ausnahme ausgeführt. Wie aus dem obigen Beispiel hervorgeht, sollte der Wert des Objekts niemals uninitialisiert und nicht zugewiesen bleiben.