Почему целые числа не кэшируются в Java? - PullRequest
25 голосов
/ 11 марта 2011

Я знаю, что похожих сообщений по этой теме, но они не совсем отвечают на мой вопрос.Когда вы делаете:

Integer a = 10;
Integer b = 10;
System.out.println("a == b: " + (a == b));

Это (по-видимому) будет печатать true большую часть времени, потому что целые числа в диапазоне [-128, 127] так или иначе кэшируются.Но:

Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println("a == b: " + (a == b));

Вернется false.Я понимаю, что я запрашиваю новые экземпляры Integer, но так как примитивы в штучной упаковке являются неизменяемыми в Java, и механизм уже существует, чтобы делать "правильные вещи" (как видно в первом случае), почему это происходит?

Разве не имеет смысла, если все экземпляры целого числа с 10 будут одним и тем же объектом в памяти?Другими словами, почему у нас нет «Integer interning», которое было бы похоже на «String interning»?

Еще лучше, разве не имело бы больше смысла, если бы примитивы в штучной упаковке представляли то же самое? независимо от значения (и типа) , один и тот же объект?Или хотя бы правильно ответьте на ==?

Ответы [ 15 ]

19 голосов
/ 12 марта 2011

Должно быть совершенно ясно, что кэширование имеет неприемлемое снижение производительности - дополнительный оператор if и поиск памяти каждый раз, когда вы создаете Integer.Это само по себе затмевает любую другую причину и остальную агонию в этой теме.

Что касается "правильного" ответа на ==, ФП ошибается в своем предположении о правильности.Целые числа правильно реагируют на == ожиданиями сообщества Java правильности и, конечно, определением корректности спецификации.То есть, если две ссылки указывают на один и тот же объект, они равны ==.Если две ссылки указывают на разные объекты, они не ==, даже если они имеют одинаковое содержимое.Таким образом, неудивительно, что new Integer(5) == new Integer(5) оценивается как false.

Более интересным вопросом является , почему new Object(); требуется каждый раз для создания уникального экземпляра?то есть почему new Object(); не разрешено кэшировать?Ответ - звонки wait(...) и notify(...).Кэширование new Object() s может привести к неправильной синхронизации потоков друг с другом, если они не должны этого делать.

Если бы не это, реализации Java могли бы полностью кэшировать new Object() s с помощью синглтона.

И это должно объяснить, почему new Integer(5), выполненное 7 раз, необходимо для создания 7 уникальных Integer объектов, каждый из которых содержит значение 5 (потому что Integer расширяет Object).


Вторичный, менее важный материал: Одна из проблем в этой, в остальном, хорошей схеме, связана с функцией автобоксирования и автопоставки.Без функции вы не могли бы сделать сравнения, такие как new Integer(5) == 5.Чтобы включить их, Java распаковывает объект (и не блокирует примитив).Поэтому new Integer(5) == 5 преобразуется в: new Integer(5).intValue() == 5не new Integer(5) == new Integer(5).

Последнее, что нужно понять, это то, что автобокс n равен , а не сделано new Integer(n). Это делается внутренне путем вызова Integer.valueOf(n).

Если вы думаете, что понимаете и хотите проверить себя, прогнозируйте выход следующей программы:

public class Foo {
  public static void main (String[] args) {
    System.out.println(Integer.valueOf(5000) == Integer.valueOf(5000));
    System.out.println(Integer.valueOf(5000) == new Integer(5000));
    System.out.println(Integer.valueOf(5000) == 5000);
    System.out.println(new Integer(5000) == Integer.valueOf(5000));
    System.out.println(new Integer(5000) == new Integer(5000));
    System.out.println(new Integer(5000) == 5000);
    System.out.println(5000 == Integer.valueOf(5000));
    System.out.println(5000 == new Integer(5000));
    System.out.println(5000 == 5000);
    System.out.println("=====");
    System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
    System.out.println(Integer.valueOf(5) == new Integer(5));
    System.out.println(Integer.valueOf(5) == 5);
    System.out.println(new Integer(5) == Integer.valueOf(5));
    System.out.println(new Integer(5) == new Integer(5));
    System.out.println(new Integer(5) == 5);
    System.out.println(5 == Integer.valueOf(5));
    System.out.println(5 == new Integer(5));
    System.out.println(5 == 5);
    System.out.println("=====");
    test(5000, 5000);
    test(5, 5);
  }
  public static void test (Integer a, Integer b) {
    System.out.println(a == b);
  }
}

Для дополнительного кредита также прогнозируйте вывод, если все == будут изменены на .equals(...)

Обновление: Благодаря комментарию от пользователя @sactiw: "диапазон кэша по умолчанию равенОт -128 до 127 и Java 1.6 и выше вы можете сбросить верхнее значение> = 127, передав -XX: AutoBoxCacheMax = из командной строки "

7 голосов
/ 11 марта 2011

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

4 голосов
/ 11 марта 2011

Если вы проверите источник, который видите:

/**
 * Returns an Integer instance representing the specified int value. If a new
 * Integer instance is not required, this method should generally be used in
 * preference to the constructor Integer(int), as this method is likely to
 * yield significantly better space and time performance by caching frequently
 * requested values.
 * 
 * @Parameters: i an int value.
 * @Returns: an Integer instance representing i.
 * @Since: 1.5
 */
 public static Integer valueOf(int i) {
      final int offset = 128;
      if (i >= -128 && i <= 127) { // must cache
          return IntegerCache.cache[i + offset];
      }
      return new Integer(i);
 }

Источник: ссылка

Это причина производительности, почему == возвращает логическое значение true с целыми числами - этоэто полностью взломатьЕсли вы хотите сравнить значения, то для этого у вас есть метод compareto или equals.

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

int - это примитивный тип, предопределенный языком и названный зарезервированным ключевым словом.В качестве примитива он не содержит информацию о классе или какой-либо другой класс.Integer - это неизменный примитивный класс, который загружается через собственный пакетный собственный механизм и преобразуется в класс - это обеспечивает автоматическую упаковку и было представлено в JDK1.5.До JDK1.5 int и Integer, где 2 очень разные вещи.

2 голосов
/ 12 марта 2011

Разве не имеет смысла, если все экземпляры целого числа с 10 будут одним и тем же объектом в памяти? Другими словами, почему у нас нет "Integer interning", который похож на "String interning"?

Потому что это было бы ужасно!

Во-первых, этот код выдаст OutOfMemoryError:

for (int i = 0; i <= Integer.MAX_VALUE; i++) {
    System.out.printf("%d\n", i);
}

Большинство объектов типа Integer, вероятно, недолговечны.

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

2 голосов
/ 11 марта 2011

Насколько я понимаю, new создаст новый объект, несмотря ни на что. Порядок операций здесь такой: сначала вы вызываете new, который создает новый объект, а затем вызывается конструктор. JVM не может вмешаться и превратить new в «захват кэшированного объекта Integer на основе значения, переданного в конструктор».

Кстати, вы рассматривали Integer.valueOf? Это работает.

2 голосов
/ 11 марта 2011

В Java каждый раз, когда вы вызываете оператор new, вы выделяете новую память и создаете новый объект . Это стандартное поведение языка, и, насколько мне известно, нет способа обойти это поведение. Даже стандартные классы должны соблюдать это правило.

1 голос
/ 08 декабря 2015

Позвольте мне немного подробнее рассказать об ответах ChrisJ и EboMike, дав ссылки на соответствующие разделы JLS.

new - ключевое слово в Java, разрешенное в выражениях создания экземпляров класса ( Раздел 15.9 JLS ).Это отличается от C ++, где new является оператором и может быть перегружен.

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

1 голос
/ 12 марта 2011

Кстати, если вы сделаете

Integer a = 234345;
Integer b = 234345;

if (a == b) {}

, возможно, это будет правдой.

Это потому, что поскольку вы не использовали новый Integer (), JVM (некод класса) разрешено кэшировать свои собственные копии целых чисел, если он сочтет нужным.Теперь вам не следует писать код, основанный на этом, но когда вы говорите «новое целое число» (234345), вы гарантируете, что у вас наверняка будут разные объекты.

1 голос
/ 12 марта 2011

new означает new.

new Object() не легкомысленно.

1 голос
/ 11 марта 2011

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

А почему Integer не работает как String?Я хотел бы представить, чтобы избежать накладных расходов на и без того медленный процесс.Причина, по которой вы используете примитивы там, где это возможно, заключается в том, что они значительно быстрее и занимают намного меньше памяти.

Изменение его сейчас может привести к поломке существующего кода, поскольку вы изменяете функциональность оператора ==.

...