Дивний бокс з Integer на 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 знаходиться в певному діапазоні? Що тут відбувається?


1
Виглядає як простак з stackoverflow.com/questions/1514910 / ...

3
@RC - Не зовсім дурний, але обговорюється подібна ситуація. Дякую за довідку, хоча.
Джоель

2
це жахливо. ось чому я ніколи не розумів сенсу всього цього примітиву, але об'єкта, але обох, але автобокс, але залежить, але aaaaaaaaargh.
njzk2

1
@Razib: Слово "автобоксинг" не є кодом, тому не форматуйте його таким чином.
Том

Відповіді:


102

trueЛінія фактично гарантується специфікацією мови. З розділу 5.1.7 :

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

Дискусія триває, припускаючи, що хоча другий рядок випуску продукції гарантований, перший не є (див. Останній абзац, цитований нижче):

В ідеалі бокс із заданим примітивним значенням p завжди давав би ідентичне посилання. На практиці це може бути неможливим з використанням існуючих методів впровадження. Наведені вище правила - це прагматичний компроміс. Заключний пункт вище вимагає, щоб певні загальні значення завжди розміщувались у нерозрізних об'єктах. Реалізація може кешувати їх, ліниво чи охоче.

Для інших значень ця формулювання вивільняє будь-які припущення щодо ідентичності коробкових значень з боку програміста. Це дозволило б (але не вимагати) спільне використання деяких або всіх цих посилань.

Це гарантує, що в більшості випадків поведінка буде бажаною, не накладаючи невиправданого покарання, особливо на невеликі пристрої. Менше обмеженої пам’яті реалізації може, наприклад, кешувати всі символи та шорти, а також цілі числа та довгі в діапазоні від -32K - + 32K.


17
Можливо, варто також відзначити, що автобоксинг насправді є лише синтаксичним цукром для виклику valueOfметоду box box (наприклад 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' типу Integer він призначається лише попередньо створеному об'єкту, на який посилається 'c'. Все це робиться JVM.

Ці правила збереження пам'яті не тільки для Integer. для збереження пам’яті два екземпляри наступних об’єктів обгортки (при створенні за допомогою боксу) завжди будуть == там, де їх примітивні значення однакові -

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

2
Longтакож має кеш із тим же діапазоном, що і Integer.
Ерік Ван

8

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


1
Цей діапазон можна розширити, використовуючи java.lang.Integer.IntegerCache.highвластивість. Цікаво, що у Лонга немає такої можливості.
Олександр Кравець

5

Так, існує дивне правило автобоксингу, яке починається, коли значення знаходяться в певному діапазоні. Якщо ви присвоюєте константу змінної Об'єкт, ніщо в мовному визначенні не говорить про те, що новий об'єкт повинен бути створений. Він може повторно використовувати наявний об'єкт із кешу.

Насправді, JVM зазвичай зберігає кеш із малих цілих цілей для цієї мети, а також таких значень, як Boolean.TRUE та Boolean.FALSE.


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

@Joel запитав зовсім іншу тему, не рівність цілої кількості, але заперечує поведінку під час виконання.
Ілля Кузнєцов

3

У Java бокс працює в інтервалі від -128 до 127 для цілого. Коли ви використовуєте цифри в цьому діапазоні, ви можете порівняти їх з оператором ==. Для об'єктів Integer за межами діапазону ви повинні використовувати рівняння.


3

Пряме присвоєння int literal посиланню на 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()метод повертає об'єкти Integer з внутрішнього, IntegerCacheякщо переданий int літерал більший -128 та менше 127.

Java кешує цілі цілі об'єкти, оскільки цей діапазон цілих чисел багато використовується в щоденному програмуванні, що опосередковано економить деяку пам'ять.

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

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

Ви можете прочитати більше в моїй статті Кеш Java Integer - Чому Integer.valueOf (127) == Integer.valueOf (127) вірно .


0

У Java 5 була введена нова функція для збереження пам’яті та підвищення продуктивності обробних об'єктів типу Integer. Цілі об'єкти кешуються внутрішньо та повторно використовуються через ті самі посилання.

  1. Це застосовно для значень цілого числа в діапазоні від –127 до +127 (значення максимального цілого).

  2. Це кешування Integer працює лише в режимі автобоксингу. Цілі об'єкти не будуть кешовані, коли вони побудовані за допомогою конструктора.

Більш детально, будь ласка, перейдіть нижче Посилання:

Цілий кеш докладно


0

Якщо ми перевіримо вихідний код Integerпредмета, то знайдемо джерело 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    

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