Странный целочисленный бокс в Java

114

Я только что видел код, похожий на этот:

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

При запуске этот блок кода распечатает:

false
true

Я понимаю, почему первый false: потому что два объекта являются отдельными объектами, поэтому ==сравнивает ссылки. Но я не могу понять, почему возвращается второй оператор true? Есть ли какое-то странное правило автобокса, которое срабатывает, когда значение Integer находится в определенном диапазоне? Что тут происходит?

Joel
источник
1
Похоже на обман stackoverflow.com/questions/1514910/…
3
@RC - Не совсем дурак, но подобная ситуация обсуждается. Тем не менее, спасибо за ссылку.
Joel
2
это ужасно. вот почему я никогда не понимал смысла всего этого примитива, кроме объекта, но и того, и другого, но автоматически, но зависит, но аааааааа
njzk2
1
@Razib: Слово «автобокс» - это не код, поэтому не форматируйте его так.
Том

Ответы:

103

trueЛиния фактически гарантируется спецификацией языка. Из раздела 5.1.7 :

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

Обсуждение продолжается, предполагая, что, хотя ваша вторая строка вывода гарантирована, первая - нет (см. Последний абзац, цитируемый ниже):

В идеале упаковка данного примитивного значения p всегда давала бы идентичную ссылку. На практике это может оказаться невозможным при использовании существующих методов реализации. Приведенные выше правила представляют собой прагматический компромисс. Последнее предложение выше требует, чтобы определенные общие значения всегда помещались в неразличимые объекты. Реализация может их кэшировать, лениво или нетерпеливо.

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

Это гарантирует, что в большинстве распространенных случаев поведение будет желаемым, без чрезмерного снижения производительности, особенно на небольших устройствах. Реализации с меньшим ограничением памяти могут, например, кэшировать все символы и короткие, а также целые и длинные числа в диапазоне от -32K до + 32K.

Джон Скит
источник
17
Также стоит отметить, что автобокс на самом деле является просто синтаксическим сахаром для вызова valueOfметода класса бокса (например, Integer.valueOf(int)). Интересно, что JLS определяет точное обессахаривание при распаковке с использованием intValue()и др., Но не обессахаривание при распаковке.
gustafc
@gustafc нет другого способа распаковать, Integerкроме как через официальный publicAPI, то есть через вызов intValue(). Но есть и другие возможные способы получить Integerэкземпляр для intзначения, например, компилятор может сгенерировать код, сохраняя и повторно используя ранее созданные Integerэкземпляры.
Хольгер
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);
   }
}

Вывод:

false
true

Да, первый вывод создается для сравнения ссылок; 'a' и 'b' - это две разные ссылки. В пункте 1 фактически создаются две ссылки, которые похожи на -

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

Второй вывод создается из-за того, что JVMпытается сохранить память, когда значение Integerпопадает в диапазон (от -128 до 127). В пункте 2 не создается новая ссылка типа Integer для 'd'. Вместо создания нового объекта для ссылочной переменной целочисленного типа 'd' ему назначается только ранее созданный объект, на который ссылается 'c'. Все это делает JVM.

Эти правила экономии памяти не только для Integer. в целях экономии памяти два экземпляра следующих объектов-оболочек (при создании с помощью упаковки) всегда будут ==, где их примитивные значения одинаковы -

  • логический
  • Байт
  • Символ от \ u0000 до \u007f(7f - 127 в десятичной системе)
  • Короткие и целые числа от -128 до 127
Razib
источник
2
Longтакже имеет кеш с тем же диапазоном, что и Integer.
Eric Wang
8

Целочисленные объекты в некотором диапазоне (я думаю, может быть, от -128 до 127) кэшируются и используются повторно. Целые числа вне этого диапазона каждый раз получают новый объект.

Адам Крум
источник
1
Этот диапазон можно расширить с помощью java.lang.Integer.IntegerCache.highсвойства. Интересно, что у Лонга такой возможности нет.
Александр Кравец
5

Да, есть странное правило автобокса, которое срабатывает, когда значения находятся в определенном диапазоне. Когда вы назначаете константу переменной Object, ничто в определении языка не говорит о необходимости создания нового объекта . Он может повторно использовать существующий объект из кеша.

Фактически, для этой цели JVM обычно хранит кеш небольших целых чисел, а также такие значения, как Boolean.TRUE и Boolean.FALSE.

Avi
источник
4

Я предполагаю, что Java хранит кеш небольших целых чисел, которые уже «упакованы», потому что они очень распространены, и это экономит чертовски много времени на повторное использование существующего объекта, чем на создание нового.

всевозможный
источник
4

Это интересный момент. В книге « Эффективная Java» предлагается всегда переопределять равенства для ваших собственных классов. Кроме того, чтобы проверить равенство двух экземпляров объекта класса java, всегда используйте метод 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));
    }
}

возвращает:

true
true
AmirHd
источник
@Joel попросил совсем другую тему, а не целочисленное равенство, а поведение во время выполнения объектов.
Илья Кузнецов
3

В Java бокс работает в диапазоне от -128 до 127 для целого числа. Когда вы используете числа в этом диапазоне, вы можете сравнить его с оператором ==. Для объектов Integer за пределами диапазона вы должны использовать equals.

Марвин
источник
3

Прямое присвоение целочисленного литерала целочисленной ссылке - это пример автоматической упаковки, когда буквальное значение в код преобразования объекта обрабатывается компилятором.

Итак, на этапе компиляции компилятор преобразуется Integer a = 1000, b = 1000;в Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Таким образом, это Integer.valueOf()метод, который фактически дает нам целочисленные объекты, и если мы посмотрим на исходный код Integer.valueOf()метода, мы ясно увидим, что метод кэширует целочисленные объекты в диапазоне от -128 до 127 (включительно).

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

Таким образом, вместо создания и возврата новых целочисленных объектов Integer.valueOf()метод возвращает целочисленные объекты из внутреннего, IntegerCacheесли переданный литерал int больше -128 и меньше 127.

Java кэширует эти целочисленные объекты, потому что этот диапазон целых чисел часто используется в повседневном программировании, что косвенно экономит память.

Кеш инициализируется при первом использовании, когда класс загружается в память из-за статического блока. Максимальным диапазоном кеша можно управлять с помощью -XX:AutoBoxCacheMaxопции JVM.

Такое кеширование не применимо только для объектов типа Integer, как и Integer.IntegerCache, который у нас также есть ByteCache, ShortCache, LongCache, CharacterCacheдляByte, Short, Long, Character соответственно.

Вы можете прочитать больше в моей статье Java Integer Cache - Почему Integer.valueOf (127) == Integer.valueOf (127) Is True .

Нареш Джоши
источник
0

В Java 5 была представлена ​​новая функция для экономии памяти и повышения производительности при обработке объектов целочисленного типа. Целочисленные объекты кэшируются внутри и повторно используются через те же объекты, на которые есть ссылки.

  1. Это применимо для целочисленных значений в диапазоне от –127 до +127 (максимальное целочисленное значение).

  2. Это целочисленное кеширование работает только при автобоксировании. Целочисленные объекты не будут кэшироваться, если они построены с использованием конструктора.

Для получения более подробной информации перейдите по ссылке ниже:

Целочисленный кэш в деталях

Рахул Маурья
источник
0

Если мы проверим исходный код объекта Integerobeject, мы найдем источник valueOfметода примерно так:

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

что может объяснить, почему Integerобъекты, которые находятся в диапазоне от -128 ( Integer.low) до 127 ( Integer.high), являются одними и теми же ссылочными объектами во время автобокса. И мы видим, что существует класс, который IntegerCacheзаботится о Integerмассиве кеша, который является частным статическим внутренним классом Integerкласса.

Есть еще один интересный пример, который может помочь нам разобраться в этой странной ситуации:

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