Dziwny boks całkowity w Javie

114

Właśnie zobaczyłem kod podobny do tego:

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);
    }
}

Po uruchomieniu ten blok kodu zostanie wydrukowany:

false
true

Rozumiem, dlaczego pierwszy jest taki false: ponieważ dwa obiekty są oddzielnymi obiektami, więc ==porównuje odniesienia. Ale nie mogę się dowiedzieć, dlaczego wraca druga instrukcja true? Czy jest jakaś dziwna reguła autoboxu, która zaczyna działać, gdy wartość całkowita jest w pewnym zakresie? Co tu się dzieje?

Joel
źródło
3
@RC - Niezupełnie oszustwo, ale podobna sytuacja jest omawiana. Dzięki za referencje.
Joel
2
to jest okropne. dlatego nigdy nie rozumiałem sensu tego całego prymitywu, ale przedmiotu, ale obu, ale auto-box, ale zależy, ale aaaaaaaaargh.
njzk2,
1
@Razib: Słowo „autoboxing” nie jest kodem, więc nie formatuj go w ten sposób.
Tom

Odpowiedzi:

103

trueLinia jest faktycznie gwarantowany przez specyfikację języka. Z sekcji 5.1.7 :

Jeśli wartość p w ramce to prawda, fałsz, bajt, znak w zakresie od \ u0000 do \ u007f lub liczba int lub krótka z zakresu od -128 do 127, niech r1 i r2 będą wynikiem dowolnych dwóch konwersji typu box z str. Zawsze jest tak, że r1 == r2.

Dyskusja toczy się dalej, sugerując, że chociaż twoja druga linia wyjścia jest gwarantowana, pierwsza nie (zobacz ostatni cytowany akapit poniżej):

Idealnie byłoby, gdyby opakowanie danej prymitywnej wartości p zawsze dawało identyczne odniesienie. W praktyce może to być niewykonalne przy użyciu istniejących technik wdrożeniowych. Powyższe zasady to pragmatyczny kompromis. Ostatnia klauzula powyżej wymaga, aby pewne wspólne wartości były zawsze opakowane w nierozróżnialne obiekty. Implementacja może je buforować leniwie lub chętnie.

W przypadku innych wartości to sformułowanie nie dopuszcza żadnych założeń dotyczących tożsamości wartości pudełkowych ze strony programisty. Pozwoliłoby to (ale nie wymagałoby) udostępnienia niektórych lub wszystkich tych odniesień.

Zapewnia to, że w większości przypadków zachowanie będzie pożądane, bez narzucania nadmiernej utraty wydajności, szczególnie na małych urządzeniach. Implementacje z mniejszą ilością pamięci mogą na przykład buforować wszystkie znaki i skróty, a także liczby całkowite i długie w zakresie od -32 000 do + 32 000.

Jon Skeet
źródło
17
Warto również zauważyć, że autoboxing jest właściwie tylko cukrem syntaktycznym do wywoływania valueOfmetody klasy box (like Integer.valueOf(int)). Interesujące jest to, że JLS definiuje dokładne usuwanie cukru z opakowania - używając intValue()et al - ale nie definiuje usuwania cukru z opakowania.
gustafc
@gustafc nie ma innego sposobu na rozpakowanie Integerniż przez oficjalne publicAPI, czyli wywołanie intValue(). Ale istnieją inne możliwe sposoby uzyskania Integerinstancji dla intwartości, np. Kompilator może wygenerować kod przechowujący i ponownie wykorzystujący wcześniej utworzone Integerinstancje.
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);
   }
}

Wynik:

false
true

Tak, pierwszy wynik jest tworzony do porównania odniesienia; „a” i „b” - to są dwa różne odniesienia. W punkcie 1 faktycznie tworzone są dwa odniesienia, które są podobne do -

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

Drugie wyjście jest generowane, ponieważ JVMpróbuje zaoszczędzić pamięć, gdy Integermieści się w zakresie (od -128 do 127). W punkcie 2 nie jest tworzone nowe odniesienie typu Integer dla „d”. Zamiast tworzyć nowy obiekt dla zmiennej referencyjnej typu Integer „d”, przypisuje się ją tylko do utworzonego wcześniej obiektu, do którego odwołuje się „c”. Wszystko to jest wykonywane przez JVM.

Te zasady oszczędzania pamięci dotyczą nie tylko liczby całkowitej. ze względu na oszczędzanie pamięci, dwie instancje następujących obiektów opakowujących (utworzonych przez opakowanie) zawsze będą ==, gdzie ich pierwotne wartości są takie same -

  • Boolean
  • Bajt
  • Znak od \ u0000 do\u007f (7f to 127 dziesiętnie)
  • Short and Integer od -128 do 127
Razib
źródło
2
Longma również pamięć podręczną z tym samym zakresem co Integer.
Eric Wang
8

Obiekty całkowite w pewnym zakresie (myślę, że może od -128 do 127) są buforowane i ponownie używane. Liczby całkowite spoza tego zakresu za każdym razem otrzymują nowy obiekt.

Adam Crume
źródło
1
Zakres ten można rozszerzyć za pomocą java.lang.Integer.IntegerCache.highwłaściwości. Ciekawe, że Long nie ma takiej opcji.
Aleksandr Kravets
5

Tak, istnieje dziwna reguła autoboxu, która działa, gdy wartości mieszczą się w pewnym zakresie. Kiedy przypisujesz stałą do zmiennej Object, nic w definicji języka nie mówi, że nowy obiekt musi zostać utworzony. Może ponownie wykorzystać istniejący obiekt z pamięci podręcznej.

W rzeczywistości JVM zazwyczaj przechowuje w tym celu pamięć podręczną małych liczb całkowitych, a także wartości, takich jak Boolean.TRUE i Boolean.FALSE.

Avi
źródło
4

Domyślam się, że Java przechowuje pamięć podręczną małych liczb całkowitych, które są już „zapakowane”, ponieważ są one bardzo powszechne i oszczędza mnóstwo czasu na ponowne wykorzystanie istniejącego obiektu niż na utworzenie nowego.

Wszelaki
źródło
4

To interesująca kwestia. W książce Efektywna Java sugeruje, aby zawsze nadpisywać równości dla własnych klas. Ponadto, aby sprawdzić równość dwóch instancji obiektów klasy Java, należy zawsze używać metody 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));
    }
}

zwroty:

true
true
AmirHd
źródło
@Joel poprosił o bardzo inny temat, nie równość liczb całkowitych, ale zachowanie obiektów w czasie wykonywania.
Iliya Kuznetsov
3

W Javie boks działa w zakresie od -128 do 127 dla liczby całkowitej. Jeśli używasz liczb z tego zakresu, możesz je porównać z operatorem ==. W przypadku obiektów typu Integer spoza zakresu musisz użyć równości.

Marvin
źródło
3

Bezpośrednie przypisanie literału int do odwołania Integer jest przykładem automatycznego pakowania, w którym wartość literału do kodu konwersji obiektu jest obsługiwana przez kompilator.

Tak więc podczas fazy kompilacji kompilator konwertuje Integer a = 1000, b = 1000;doInteger a = Integer.valueOf(1000), b = Integer.valueOf(1000); .

Jest to więc Integer.valueOf()metoda, która faktycznie daje nam obiekty będące liczbami całkowitymi, a jeśli spojrzymy na kod źródłowy Integer.valueOf()metody, wyraźnie widać, że metoda buforuje obiekty typu integer w zakresie od -128 do 127 (włącznie).

/**
 *
 * 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);
 }

Dlatego zamiast tworzyć i zwracać nowe obiekty całkowite, Integer.valueOf()metoda zwraca obiekty typu Integer z wewnętrznego, IntegerCachejeśli przekazany literał int jest większy niż -128 i mniejszy niż 127.

Java buforuje te obiekty liczb całkowitych, ponieważ ten zakres liczb całkowitych jest często używany w codziennym programowaniu, co pośrednio oszczędza trochę pamięci.

Pamięć podręczna jest inicjowana przy pierwszym użyciu, gdy klasa zostanie załadowana do pamięci ze względu na blok statyczny. Maksymalny zakres pamięci podręcznej można kontrolować za pomocą -XX:AutoBoxCacheMaxopcji JVM.

Takie zachowanie buforowania nie dotyczy tylko obiektów Integer, podobnie Integer.IntegerCache mamy także ByteCache, ShortCache, LongCache, CharacterCachedlaByte, Short, Long, Character odpowiednio.

Możesz przeczytać więcej na temat mojego artykułu Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .

Naresh Joshi
źródło
0

W Javie 5 wprowadzono nową funkcję oszczędzającą pamięć i poprawiającą wydajność obsługi obiektów typu Integer. Obiekty całkowite są buforowane wewnętrznie i ponownie używane za pośrednictwem tych samych obiektów, do których istnieją odniesienia.

  1. Ma to zastosowanie do wartości całkowitych z zakresu od –127 do +127 (maksymalna wartość całkowita).

  2. Buforowanie typu Integer działa tylko w przypadku autoboxingu. Obiekty całkowite nie będą buforowane, gdy zostaną zbudowane przy użyciu konstruktora.

Aby uzyskać więcej informacji, przejdź przez poniższy Link:

Całkowita pamięć podręczna w szczegółach

Rahul Maurya
źródło
0

Jeśli sprawdzimy kod źródłowy Integerobeject, znajdziemy źródło valueOfmetody tak:

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

co może wyjaśnić, dlaczego Integerobiekty, które są w zakresie od -128 ( Integer.low) do 127 ( Integer.high), są tymi samymi obiektami, do których istnieją odniesienia podczas autoboxingu. I widzimy, że istnieje klasa IntegerCachezajmuje się Integertablicą pamięci podręcznej, która jest prywatną statyczną wewnętrzną klasą Integerklasy.

Jest jeszcze jeden interesujący przykład, który może pomóc nam zrozumieć tę dziwną sytuację:

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
źródło