Java Language
Autoboxing
Szukaj…
Wprowadzenie
Autoboxing to automatyczna konwersja dokonywana przez kompilator Java między typami pierwotnymi i odpowiadającymi im klasami opakowań obiektów. Przykład, konwersja int -> Integer, double -> Double ... Jeśli konwersja przebiega w drugą stronę, nazywa się to rozpakowaniem. Zwykle jest to używane w kolekcjach, które nie mogą pomieścić innych niż obiekty, w których potrzebne są pierwotne typy boksu przed ustawieniem ich w kolekcji.
Uwagi
Automatyczne sprawdzanie może mieć problemy z wydajnością, jeśli jest często używane w kodzie.
Używanie int i Integer zamiennie
Kiedy używasz typów ogólnych z klasami użyteczności, często możesz stwierdzić, że typy liczbowe nie są zbyt pomocne, gdy są określone jako typy obiektów, ponieważ nie są one równe ich prymitywnym odpowiednikom.
List<Integer> ints = new ArrayList<Integer>();
List<Integer> ints = new ArrayList<>();
Na szczęście wyrażenia, które oceniają na int
mogą być użyte zamiast liczby Integer
gdy jest to potrzebne.
for (int i = 0; i < 10; i++)
ints.add(i);
The ints.add(i);
instrukcja jest równoważna z:
ints.add(Integer.valueOf(i));
I zachowuje właściwości z Integer#valueOf
takie jak posiadanie tych samych obiektów Integer
buforowanych przez JVM, gdy znajduje się w zakresie buforowania liczb.
Dotyczy to również:
-
byte
iByte
-
short
iShort
-
float
iFloat
-
double
iDouble
-
long
iLong
-
char
iCharacter
-
boolean
iBoolean
Należy jednak zachować ostrożność w niejednoznacznych sytuacjach. Rozważ następujący kod:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
ints.remove(1); // ints is now [1, 3]
Interfejs java.util.List
zawiera zarówno remove(int index)
(metoda interfejsu List
), jak i remove(Object o)
(metoda odziedziczona z java.util.Collection
). W tym przypadku nie następuje boksowanie i wywoływane jest remove(int index)
.
Jeszcze jeden przykład dziwnego zachowania kodu Java spowodowanego przez automatyczne łączenie liczb całkowitych o wartościach w zakresie od -128
do 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
Dzieje się tak, ponieważ >=
operator domyślnie wywołuje intValue()
która zwraca int
podczas gdy ==
porównuje referencje , a nie wartości int
.
Domyślnie Java buforuje wartości w zakresie [-128, 127]
, więc operator ==
działa, ponieważ liczby Integers
w tym zakresie odnoszą się do tych samych obiektów, jeśli ich wartości są takie same. Maksymalną wartość zakresu buforowanego można zdefiniować za -XX:AutoBoxCacheMax
opcji -XX:AutoBoxCacheMax
JVM. Jeśli więc uruchomisz program z -XX:AutoBoxCacheMax=1000
, następujący kod zostanie wydrukowany jako true
:
Integer a = 1000;
Integer b = 1000;
System.out.println(a == b); // true
Użycie Boolean w instrukcji if
Ze względu na automatyczne rozpakowywanie można użyć wartości Boolean
w instrukcji if
:
Boolean a = Boolean.TRUE;
if (a) { // a gets converted to boolean
System.out.println("It works!");
}
Działa to przez while
, do while
i warunek w instrukcjach for
.
Należy zauważyć, że jeśli wartość Boolean
ma null
, podczas NullPointerException
zostanie NullPointerException
.
Automatyczne rozpakowywanie może prowadzić do wyjątku NullPointerException
Ten kod kompiluje:
Integer arg = null;
int x = arg;
Ale zawiesi się w czasie wykonywania z java.lang.NullPointerException
w drugiej linii.
Problem polega na tym, że pierwotna int
nie może mieć wartości null
.
Jest to przykład minimalistyczny, ale w praktyce często przejawia się w bardziej wyrafinowanych formach. NullPointerException
nie jest zbyt intuicyjny i często nie pomaga w zlokalizowaniu takich błędów.
Ostrożnie polegaj na autoboksowaniu i automatycznym rozpakowywaniu, upewnij się, że rozpakowane wartości nie będą miały wartości null
w czasie wykonywania.
Pamięć i narzut obliczeniowy autoboxingu
Automatyczne przeszukiwanie może wiązać się z dużym nakładem pamięci. Na przykład:
Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
square.put(i, i * i); // Autoboxing of large integers
}
zwykle zużywa znaczną ilość pamięci (około 60 KB na 6 KB rzeczywistych danych).
Ponadto, liczby całkowite w pudełkach zwykle wymagają dodatkowych przejazdów w obie strony w pamięci, przez co pamięci podręczne procesora są mniej skuteczne. W powyższym przykładzie dostęp do pamięci jest rozłożony na pięć różnych lokalizacji, które mogą znajdować się w całkowicie różnych obszarach pamięci: 1. obiekt HashMap
, 2. obiekt Entry[] table
mapy, 3. obiekt Entry
, 4. 5. obiekt key
(bokowanie key
pierwotnego), 5. obiekt value
wejścia (bokowanie value
pierwotnej).
class Example {
int primitive; // Stored directly in the class `Example`
Integer boxed; // Reference to another memory location
}
Czytanie w boxed
wymaga dwóch dostępów do pamięci, dostęp do primitive
tylko jednego.
Podczas pobierania danych z tej mapy pozornie niewinny kod
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(i);
}
jest równa:
int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}
Zazwyczaj powyższy kod powoduje tworzenie i odśmiecanie obiektu typu Integer
dla każdej operacji Map#get(Integer)
. (Aby uzyskać więcej informacji, patrz uwaga poniżej).
Aby zmniejszyć ten narzut, kilka bibliotek oferuje zoptymalizowane kolekcje dla typów pierwotnych, które nie wymagają boksu. Oprócz uniknięcia narzutów związanych z boksem kolekcja ta będzie wymagać około 4x mniej pamięci na wpis. Podczas gdy Java Hotspot może być w stanie zoptymalizować autoboxing, pracując z obiektami na stosie zamiast na stercie, nie jest możliwe zoptymalizowanie narzutu pamięci i wynikającej z tego pośredniej pamięci.
Strumienie Java 8 mają również zoptymalizowane interfejsy dla prymitywnych typów danych, takich jak IntStream
, które nie wymagają boksu.
Uwaga: typowe środowisko wykonawcze Java utrzymuje prostą pamięć podręczną liczby Integer
i innych pierwotnych obiektów opakowujących, które są używane przez metody fabryczne valueOf
i przez autoboxing. W przypadku liczby Integer
domyślny zakres tej pamięci podręcznej wynosi od -128 do +127. Niektóre maszyny JVM zapewniają opcję wiersza polecenia JVM do zmiany rozmiaru / zakresu pamięci podręcznej.
Różne przypadki, gdy liczby całkowite i int mogą być używane zamiennie
Przypadek 1: Podczas używania zamiast argumentów metody.
Jeśli metoda wymaga obiektu klasy opakowania jako argumentu. Następnie zamiennie argumentowi można przekazać zmienną odpowiedniego typu pierwotnego i odwrotnie.
Przykład:
int i;
Integer j;
void ex_method(Integer i)//Is a valid statement
void ex_method1(int j)//Is a valid statement
Przypadek 2: Podczas przekazywania zwracanych wartości:
Gdy metoda zwraca zmienną typu pierwotnego, wówczas obiekt odpowiedniej klasy opakowania może być przekazywany jako wartość zwracana zamiennie i odwrotnie.
Przykład:
int i;
Integer j;
int ex_method()
{...
return j;}//Is a valid statement
Integer ex_method1()
{...
return i;//Is a valid statement
}
Przypadek 3: Podczas wykonywania operacji.
Podczas wykonywania operacji na liczbach zmienną typu pierwotnego i obiekt odpowiedniej klasy opakowania można stosować zamiennie.
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
Pitfall : Pamiętaj, aby zainicjować lub przypisać wartość do obiektu klasy otoki.
Używając zamiennie obiektu klasy opakowującej i pierwotnej zmiennej, nigdy nie zapomnij lub nie przegap, aby zainicjować lub przypisać wartość do obiektu klasy opakowującej, w przeciwnym razie może to spowodować wyjątek wskaźnika zerowego w czasie wykonywania.
Przykład:
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
}
W powyższym przykładzie wartość obiektu jest nieprzypisana i niezainicjowana, dlatego w czasie wykonywania program będzie działał jako wyjątek wskaźnika zerowego. Tak więc, jak wynika z powyższego przykładu, wartość obiektu nigdy nie powinna pozostać niezainicjowana i nieprzypisana.