Dlaczego 128 == 128 jest fałszywe, ale 127 == 127 jest prawdziwe podczas porównywania opakowań typu Integer w Javie?

172
class D {
    public static void main(String args[]) {
        Integer b2=128;
        Integer b3=128;
        System.out.println(b2==b3);
    }
}

Wynik:

false

class D {
    public static void main(String args[]) {
        Integer b2=127;
        Integer b3=127;
        System.out.println(b2==b3);
    }
}

Wynik:

true

Uwaga: liczby od -128 do 127 są prawdziwe.

vipin k.
źródło
10
Możesz znaleźć bexhuff.com/2006/11/java-1-5-autoboxing-wackyness informacyjne.
Dominic Rodger
1
jak doszedłeś do punktu, aby zadać to pytanie? to naprawdę fajne, ale nigdy nie można spotkać czegoś takiego „w prawdziwym świecie”… czy?
Mare Infinitus

Odpowiedzi:

217

Kiedy kompilujesz literał liczbowy w Javie i przypisujesz go do Integer (kapitału I), kompilator emituje:

Integer b2 =Integer.valueOf(127)

Ta linia kodu jest również generowana podczas korzystania z autoboxingu.

valueOf jest zaimplementowany w taki sposób, że pewne liczby są „łączone” i zwraca to samo wystąpienie dla wartości mniejszych niż 128.

Z kodu źródłowego Java 1.6, wiersz 621:

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

Wartość highmożna skonfigurować na inną wartość za pomocą właściwości systemowej.

-Djava.lang.Integer.IntegerCache.high = 999

Jeśli uruchomisz program z tą właściwością systemową, wyjdzie prawda!

Oczywisty wniosek: nigdy nie polegaj na tym, że dwa odniesienia są identyczne, zawsze porównuj je z .equals()metodą.

Więc b2.equals(b3)wypisze prawdę dla wszystkich logicznie równych wartości b2, b3.

Zauważ, że Integerpamięć podręczna nie istnieje ze względu na wydajność, ale raczej w celu dostosowania się do JLS, sekcja 5.1.7 ; tożsamość obiektu należy podać dla wartości od -128 do 127 włącznie.

Integer # valueOf (int) również dokumentuje to zachowanie:

metoda ta prawdopodobnie zapewni znacznie lepszą wydajność w zakresie przestrzeni i czasu dzięki buforowaniu często żądanych wartości. Ta metoda zawsze buforuje wartości z zakresu od -128 do 127 włącznie i może buforować inne wartości spoza tego zakresu.

Andreas Petersson
źródło
1
Zwróć uwagę, że wartości mniejsze niż 127 będą ignorowane przez Javę, a wartości większe niż Integer.MAX_VALUE-128 będą ograniczone.
Andreas Petersson
Liczby całkowite są buforowane dla wartości bajtów w Javie 5 i nowszych, tworząc nową Integer (1) == new Integer (1). Jednak nie dotyczy to Java 1.4 lub starszej, więc uważaj, jeśli ostatecznie musisz przejść na to środowisko.
MetroidFan2002
11
nie, to jest złe. new Integer (1) == new Integer (1) jest fałszem niezależnie od jvm. AFAIK żaden kompilator nie oszuka słowa kluczowego „nowe”. MUSI zawsze utworzyć instancję nowego obiektu.
Andreas Petersson
1
@Holger ciekawy punkt. Ale technicznie możliwe jest zastąpienie klasy Integer z JDK niestandardowym impl ... (nie pytaj, dlaczego ktoś byłby tak szalony) - wtedy może to mieć skutki uboczne, których nie można zoptymalizować
Andreas Petersson
1
@AndreasPetersson pewnie. „Kompilator” oznacza kompilator JIT, który dokładnie zna aktualną klasę implementacji i może optymalizować tylko wtedy, gdy konstruktor nie ma żadnych skutków ubocznych. Lub zoptymalizuj wyrażenie, aby tylko odtworzyć efekty uboczne, a następnie użyj false. Właściwie może się to zdarzyć już dzisiaj, jako efekt uboczny zastosowania analizy ucieczki i zamiany skalarnej.
Holger
24

Buforowanie autoboxu od -128 do 127. Jest to określone w JLS ( 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 między -128 a 127, to niech r1 i r2 będą wynikiem dowolnych dwóch konwersji typu box z str. Zawsze jest tak, że r1 == r2.

Prosta zasada do zapamiętania podczas pracy z obiektami to - użyj, .equalsjeśli chcesz sprawdzić, czy dwa obiekty są „równe”, użyj, ==gdy chcesz sprawdzić, czy wskazują na tę samą instancję.

Michael Lloyd Lee mlk
źródło
1
Uwaga: JLS zmienił się w Javie 9. Jest to teraz gwarantowane tylko dla wyrażeń stałych czasu kompilacji ; zobacz aktualizację do zaakceptowanej odpowiedzi.
Stephen C
9

Użycie pierwotnych typów danych, ints, dałoby w obu przypadkach wartość true, oczekiwany wynik.

Jednak ponieważ używasz obiektów typu Integer, operator == ma inne znaczenie.

W kontekście obiektów == sprawdza, czy zmienne odnoszą się do tego samego odniesienia do obiektu.

Aby porównać wartości obiektów należy użyć metody equals () np

 b2.equals(b1)

który wskaże, czy b2 jest mniejsze niż b1, większe lub równe (szczegóły w API)

chrisbunney
źródło
7

Jest to optymalizacja pamięci w języku Java.

Aby zaoszczędzić na pamięci, Java ponownie wykorzystuje wszystkie obiekty opakowania, których wartości mieszczą się w następujących zakresach:

Wszystkie wartości logiczne (prawda i fałsz)

Wszystkie wartości w bajtach

Wszystkie wartości znaków od \ u0000 do \ u007f (tj. Od 0 do 127 dziesiętnie)

Wszystkie wartości Short i Integer od -128 do 127.

Deweloper Marius Žilėnas
źródło
3

Spójrz na Integer.java, jeśli wartość mieści się w zakresie od -128 do 127, użyje puli pamięci podręcznej, więc (Integer) 1 == (Integer) 1podczas(Integer) 222 != (Integer) 222

 /**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * 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) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}       
yanghaogn
źródło
0

Inne odpowiedzi opisują, dlaczego można zaobserwować zaobserwowane efekty, ale to naprawdę nie ma znaczenia dla programistów (z pewnością interesujące, ale o czym należy zapomnieć podczas pisania właściwego kodu).

Aby porównać obiekty typu Integer pod kątem równości, użyj equalsmetody.

Nie próbować porównać obiektów Integer równości za pomocą operatora tożsamości, ==.

Może się zdarzyć, że niektóre równe wartości są identycznymi obiektami, ale nie jest to coś, na czym należy polegać.

user13463803
źródło
-4

Napisałem co następuje, ponieważ ten problem nie dotyczy tylko liczby Integer. Mój wniosek jest taki, że częściej niż nie, jeśli używasz API nieprawidłowo, nadal widzisz nieprawidłowe zachowanie. Użyj go poprawnie i powinieneś zobaczyć poprawne zachowanie:

public static void main (String[] args) {
    Byte b1=127;
    Byte b2=127;

    Short s1=127; //incorrect should use Byte
    Short s2=127; //incorrect should use Byte
    Short s3=128;
    Short s4=128;

    Integer i1=127; //incorrect should use Byte
    Integer i2=127; //incorrect should use Byte
    Integer i3=128;
    Integer i4=128;

    Integer i5=32767; //incorrect should use Short
    Integer i6=32767; //incorrect should use Short

    Long l1=127L;           //incorrect should use Byte
    Long l2=127L;           //incorrect should use Byte
    Long l3=13267L;         //incorrect should use Short
    Long l4=32767L;         //incorrect should use Short
    Long l5=2147483647L;    //incorrect should use Integer 
    Long l6=2147483647L;    //incorrect should use Integer
    Long l7=2147483648L;
    Long l8=2147483648L;

    System.out.print(b1==b2); //true  (incorrect) Used API correctly
    System.out.print(s1==s2); //true  (incorrect) Used API incorrectly
    System.out.print(i1==i2); //true  (incorrect) Used API incorrectly
    System.out.print(l1==l2); //true  (incorrect) Used API incorrectly

    System.out.print(s3==s4); //false (correct) Used API correctly
    System.out.print(i3==i4); //false (correct) Used API correctly
    System.out.print(i5==i6); //false (correct) Used API correctly
    System.out.print(l3==l4); //false (correct) Used API correctly
    System.out.print(l7==l8); //false (correct) Used API correctly
    System.out.print(l5==l6); //false (correct) Used API incorrectly

}
thejartender
źródło