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>();
Java SE 7
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 i Byte
  • short i Short
  • float i Float
  • double i Double
  • long i Long
  • char i Character
  • boolean i Boolean

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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow