Чому 128 == 128 помилково, але 127 == 127 відповідає дійсності при порівнянні обгортки Integer на Java?


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

Вихід:

false

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

Вихід:

true

Примітка. Цифри від -128 до 127 відповідають дійсності.


10
Ви можете знайти bexhuff.com/2006/11/java-1-5-autoboxing-wackyness інформативним.
Домінік Роджер

1
як ти дійшов до питання, щоб задати це питання? це дійсно весело, але ніколи не трапляється щось подібне "в реальному світі" ... чи?
Mare Infinitus

Відповіді:


217

Коли ви компілюєте числовий літерал на Java та призначаєте його цілому (капіталу I), компілятор випускає:

Integer b2 =Integer.valueOf(127)

Цей рядок коду також генерується при використанні автобоксингу.

valueOf реалізований таким чином, що певні числа "об'єднані", і він повертає той самий екземпляр для значень, менших ніж 128.

З вихідного коду java 1.6, рядок 621:

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

Значення highможе бути налаштоване на інше значення із властивістю системи.

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

Якщо ви запускаєте свою програму із цим системним властивістю, вона виводитиметься правдою!

Очевидний висновок: ніколи не покладайтеся на те, що дві посилання є однаковими, завжди порівнюйте їх із .equals()методом.

Таким чином b2.equals(b3)буде надруковано true для всіх логічно рівних значень b2, b3.

Зауважте, що Integerкеш не існує з міркувань продуктивності, а скоріше, щоб він відповідав JLS, розділ 5.1.7 ; ідентифікатор об'єкта повинен бути вказаний для значень від -128 до 127 включно.

Integer # valueOf (int) також документує таку поведінку:

цей метод, ймовірно, дає значно кращі показники простору та часу, кешуючи часто запитувані значення. Цей метод завжди буде кешувати значення в діапазоні від -128 до 127, включно, і може кешувати інші значення поза цим діапазоном.


1
зауважте, що значення меншими ніж 127 будуть ігноровані java, а значення, що перевищують Integer.MAX_VALUE-128 буде обмежено.
Андреас Петерсон

Цілі числа кешуються за значеннями байтів у Java 5 та новіших версіях, створюючи новий Integer (1) == новий Integer (1). Однак це не так у Java 1.4 або новіших версіях, тому будьте обережні, якщо вам доведеться з часом повернутися до цього середовища.
MetroidFan2002

11
ні, це неправильно. new Integer (1) == new Integer (1) є помилковим незалежно від jvm. AFAIK жоден компілятор не обдурить "нове" ключове слово. Він ОБОВ'ЯЗКОВО завжди повинен створювати новий об'єкт.
Андреас Петерсон

1
@Holger цікавий момент. Але це технічно можливо замінити клас Integer з JDK з призначеної для користувача осущ ... (не питайте , чому хтось - то буде що неосудним) - то це може мати побічні ефекти, які не дозволили оптимізують геть
Andreas Петерсона

1
@AndreasPetersson впевнений. "Компілятор" означає компілятор JIT, який точно знає фактичний клас реалізації та може оптимізувати лише у тому випадку, якщо у конструктора немає побічних ефектів. Або оптимізуйте вираз лише для відтворення побічних ефектів з подальшим використанням false. Насправді це вже може статися сьогодні, як побічний ефект від застосування аналізу втечі та скалярної заміни.
Хольгер

24

Автоматичне кешування кешів від -128 до 127. Це визначено в JLS ( 5.1.7 ).

Якщо значення p, яке міститься у вікні, є істинним, хибним, байтом, знаком в діапазоні \ u0000 до \ u007f або int або коротким числом від -128 до 127, то нехай r1 і r2 є результатами будь-яких двох перетворень боксу з р. Це завжди так, що r1 == r2.

Просте правило, яке слід пам’ятати при роботі з об’єктами, - використовувати, .equalsякщо ви хочете перевірити, чи обидва об’єкти «рівні», використовуйте, ==коли ви хочете побачити, чи вказують вони на один і той же екземпляр.


1
Примітка: JLS змінився в Java 9. Це тепер гарантується лише для постійних виразів часу компіляції ; див. оновлення до прийнятої відповіді.
Стівен C

9

Використання примітивних типів даних, ints, дасть би правду в обох випадках очікуваного результату.

Однак, оскільки ви використовуєте об'єкти Integer, оператор == має інше значення.

У контексті об'єктів == перевіряє, чи змінні посилаються на ту саму посилання на об'єкт.

Для порівняння значення об'єктів слід використовувати метод equals () Напр

 b2.equals(b1)

який вказуватиме, чи b2 менше b1, більше або дорівнює (перевірте API для деталей)


7

Це оптимізація пам'яті на Java.

Щоб заощадити на пам'яті, Java "повторно використовує" всі об'єкти обгортки, значення яких потрапляють у наступні діапазони:

Усі булеві значення (істинні та хибні)

Усі значення байтів

Усі значення символів від \ u0000 до \ u007f (тобто від 0 до 127 у десятковій кількості)

Усі короткі та цілі значення від -128 до 127.


3

Подивіться на Integer.java, якщо значення знаходиться в межах від -128 до 127, воно використовуватиме кешований пул, тож (Integer) 1 == (Integer) 1поки(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);
}       

0

Інші відповіді описують, чому можна спостерігати спостережувані ефекти, але це дійсно поза точкою для програмістів (цікаво, звичайно, але щось, про що слід забути, все про те, як писати фактичний код).

Для порівняння об'єктів Integer для рівності використовуйте equalsметод.

Не намагайтеся порівнювати об'єкти Integer для рівності за допомогою оператора ідентичності, ==.

Може статися, що деякі рівні значення є однаковими об'єктами, але це не те, на що, як правило, слід покладатися.


-4

Я написав наступне, оскільки ця проблема не характерна лише для Integer. Мій висновок полягає в тому, що найчастіше, якщо ви використовуєте API неправильно, ви бачите неправильну поведінку. Використовуйте його правильно, і ви повинні побачити правильну поведінку:

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.