Почему в Java не кэшируются целые элементы?

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

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, и механизм уже существует, чтобы делать "правильную вещь" (как видно в первом случае), почему это происходит?

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

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

Ответ 1

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

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

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

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

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


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

Последнее, что нужно понять, это то, что autoboxing 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 вперед, вы можете reset верхнее значение >= 127 путем передачи -XX: AutoBoxCacheMax = из командной строки

Ответ 2

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

Ответ 3

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

/**
 * 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 очень разные вещи.

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

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

Сначала этот код выкинул бы OutOfMemoryError:

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

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

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

Ответ 7

Новый экземпляр - это новый экземпляр, поэтому они равны по значению, но они не равны объектам.

Итак a == b не может вернуть true.

Если они были 1 объектом, как вы просите: a+=2; добавит 2 ко всем int = 10 - это будет ужасно.

Ответ 8

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

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

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

Ответ 9

new означает new.

new Object() не является фривольным.

Ответ 10

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

Integer a = 234345;
Integer b = 234345;

if (a == b) {}

возможно, что это будет верно.

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

Ответ 11

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

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

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

Ответ 12

Для объектов Integer для сравнения используется условие a.equals(b).

Компилятор не будет выполнять распаковку для вас, пока вы сравниваете, если вы не назначили значение базовому типу.

Ответ 13

Предполагая, что вы точно описываете поведение вашего кода, это звучит так, как автобоксинг не работает на "gets" (=) operatior, вместо этого он звучит как Integer x = 10; дает объект xa указатель памяти "10" вместо десятичной доли 10. Поэтому ((a == b) == true) (будет оцениваться как true, потому что == на объектах работает на адресах памяти, которые вы назначили как 10.

Итак, когда вы должны использовать autoboxing и unboxing? Используйте их только при наличии "несоответствия импеданса" между ссылочными типами и примитивами, например, когда вам нужно ввести числовые значения в коллекцию. Нецелесообразно использовать автобоксинг и распаковку для научных вычислений или другой высокопроизводительный численный код. Целое число не является заменой int; autoboxing и unboxing размывают различие между примитивными типами и ссылочными типами, но они не устраняют его.

Что говорит оракул по этому вопросу.

Обратите внимание, что в документации нет примеров с оператором '='.

Ответ 14

Также обратите внимание на то, что диапазон кеша был от -128 до 127 в Java 1.5, но Java 1.6 - это диапазон по умолчанию, то есть вы можете установить верхнее значение >= 127, передав -XX: AutoBoxCacheMax = new_limit из командной строки

Ответ 15

Это потому, что вы используете инструкцию new для построения объектов.

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

Это напечатает true. Странно, но Java.