Почему 128 == 128 ложно, а 127 == 127 верно при сравнении оболочек Integer в Java?

173
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 соответствуют действительности.

Vipin K.
источник
10
Вы можете найти bexhuff.com/2006/11/java-1-5-autoboxing-wackyness информативным.
Доминик Роджер
1
как ты добрался до того, чтобы задать этот вопрос? это действительно весело, но никто никогда не сталкивался с чем-то подобным "в реальном мире" ... или?
Mare Infinitus

Ответы:

218

Когда вы компилируете числовой литерал в 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

Если вы запустите вашу программу с этим системным свойством, она выдаст true!

Очевидный вывод: никогда не полагайтесь на две одинаковые ссылки, всегда сравнивайте их с .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) == new Integer (1). Однако это не относится к Java 1.4 или ниже, поэтому будьте осторожны, если вам придется в конечном итоге перейти к этой среде.
MetroidFan2002
11
нет это неправильно new Integer (1) == new Integer (1) равно false независимо от jvm. AFAIK никакой компилятор не будет обманывать ключевое слово "new". он ДОЛЖЕН всегда создавать экземпляр нового объекта.
Андреас Петерссон
1
@ Хольгер интересный момент. Но технически возможно заменить класс Integer из JDK на пользовательский импл ... (не спрашивайте, почему кто-то будет таким безумным) - тогда у него могут быть побочные эффекты, которые не могут быть оптимизированы
Andreas Petersson
1
@AndreasPetersson уверен. «Компилятор» означает JIT-компилятор, который точно знает фактический класс реализации и может оптимизироваться, только если конструктор не имеет побочных эффектов. Или оптимизируйте выражение, чтобы воспроизвести только побочные эффекты с последующим использованием false. На самом деле, это может произойти уже сегодня, как побочный эффект применения Escape-анализа и скалярной замены.
Хольгер
24

Автобокс кэширует от -128 до 127. Это указано в JLS ( 5.1.7 ).

Если значение p в штучной упаковке является истинным, ложным, байтом, символом в диапазоне от \ u0000 до \ u007f или целым или коротким числом от -128 до 127, то пусть r1 и r2 будут результатами любых двух преобразований бокса из р. Это всегда тот случай, когда r1 == r2.

Простое правило, которое нужно помнить при работе с объектами: используйте, .equalsесли хотите проверить, равны ли два объекта, используйте, ==если хотите увидеть, указывают ли они на один и тот же экземпляр.

Майкл Ллойд Ли млк
источник
1
Примечание: JLS изменился в Java 9. Теперь это гарантировано только для константных выражений времени компиляции ; см. обновление до принятого ответа.
Стивен С.
9

Использование примитивных типов данных, int, даст в обоих случаях ожидаемый результат.

Однако, поскольку вы используете объекты Integer, оператор == имеет другое значение.

В контексте объектов == проверяет, ссылаются ли переменные на одну и ту же ссылку на объект.

Для сравнения значения объектов следует использовать метод equals (), например

 b2.equals(b1)

который укажет, меньше ли b2, чем b1, больше или равно (проверьте API)

chrisbunney
источник
7

Это связано с оптимизацией памяти в Java.

Для экономии памяти Java «повторно» использует все объекты-оболочки, значения которых попадают в следующие диапазоны:

Все логические значения (true и false)

Все байтовые значения

Все значения символов от \ u0000 до \ u007f (то есть от 0 до 127 в десятичном виде)

Все значения Short и Integer от -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);
}       
yanghaogn
источник
0

В других ответах описывается, почему наблюдаемые эффекты можно наблюдать, но это не относится к делу программистов (конечно, это интересно, но то, о чем вы должны забыть при написании реального кода).

Чтобы сравнить целочисленные объекты на равенство, используйте equalsметод.

Не пытайтесь сравнивать объекты Integer на равенство, используя оператор тождественности ==.

Может случиться, что некоторые равные значения являются идентичными объектами, но это не то, на что обычно следует полагаться.

user13463803
источник
-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

}
thejartender
источник