Weird Integer бокс в Java

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

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? Есть ли какое-то странное правило autoboxing, которое срабатывает, когда значение Integer находится в определенном диапазоне? Что здесь происходит?

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

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

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

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

  • логический
  • Байт
  • Символ от \ u0000 до \u007f (7f - 127 в десятичном виде)
  • Короткое и целое от -128 до 127

Ответ 3

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

Ответ 4

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

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

Ответ 5

Это интересный момент. В книге "Эффективная 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

Ответ 6

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

Ответ 7

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

Ответ 8

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

Таким образом, во время фазы компиляции компилятор преобразует 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) - True.

Ответ 9

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

  • Это применимо для значений Integer в диапазоне от -127 до +127 (Максимальное целочисленное значение).

  • Это кэширование Integer работает только при автобоксинге. Объекты Integer будут не кэшироваться, когда они создаются с использованием конструктора.

Подробнее см. ниже. Ссылка:

Подробный кеш-ключ

Ответ 10

Если мы проверим исходный код Integer obeject, мы найдем источник метода 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    

}