Почему компилятор /JVM просто не делает автобоксинг "просто работать"?

Автобоксирование довольно страшно. В то время как я полностью понимаю разницу между == и .equals, я не могу не помочь, так как из-за меня все в порядке:

    final List<Integer> foo = Arrays.asList(1, 1000);
    final List<Integer> bar = Arrays.asList(1, 1000);
    System.out.println(foo.get(0) == bar.get(0));
    System.out.println(foo.get(1) == bar.get(1));

Что печатает

true
false

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

Печать ложных или истинных истин была бы лучше.

ИЗМЕНИТЬ

Я не согласен с поломкой старого кода. Имея foo.get(0) == bar.get(0) return true, вы уже нарушили код.

Невозможно ли это решить на уровне компилятора, заменив Integer на int в байтовом коде (если он никогда не назначен null)

Ответ 1

  • Почему они это сделали?

Каждое целое число от -128 до 127 кэшируется java. Они сделали это, предположительно, для повышения производительности. Даже если бы они хотели вернуться к этому решению сейчас, вряд ли они это сделают. Если кто-то создал код в зависимости от этого, их код сломался бы, когда он был вынут. Для кодирования хобби это, возможно, не имеет значения, но для корпоративного кода люди расстраиваются, и судебные процессы происходят.

  • Почему они не кэшируют все целые числа, используемые программой?

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

  • Почему JVM всегда автоматически распаковывается в примитив?

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

Если JVM автоматически распаковывается в примитивы при вызовах ==, эта проблема фактически станет БОЛЕЕ запутанной. Теперь вам нужно помнить, что == всегда сравнивает ссылки на объекты, если объекты не могут быть распакованы. Это вызовет еще более странные запутывающие случаи, как тот, который вы указали выше.

Вместо этого слишком беспокоиться об этом, просто запомните это правило:

НИКОГДА сравнивать объекты с ==, если вы не собираетесь сравнивать их по их ссылкам. Если вы это сделаете, я не могу придумать сценарий, в котором вы столкнулись с проблемой.

Ответ 2

Можете ли вы представить себе, насколько плохая производительность была бы, если бы каждый Integer переносил служебные данные для интернирования? Также не работает для new Integer.

Язык Java (а не проблема JVM) не всегда может автоматически распаковываться, потому что код, предназначенный для pre-1.5 Java, должен по-прежнему работать.

Ответ 3

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

И из здесь

Результат всей этой магии состоит в том, что вы можете в значительной степени игнорировать различие между int и Integer с несколькими оговорками. Выражение Integer может иметь нулевое значение. Если ваша программа пытается autounbox null, она выкинет исключение NullPointerException. Оператор == выполняет сравнения ссылочной идентичности по выражениям Integer и сравнениям значений равенства по выражениям int. Наконец, есть затраты на производительность, связанные с боксом и распаковкой, даже если это делается автоматически

Ответ 4

Если вы полностью откажитесь от автобоксинга, вы все равно получите это поведение.

final List<Integer> foo =
  Arrays.asList(Integer.valueOf( 1 ), Integer.valueOf( 1000 ));
final List<Integer> bar =
  Arrays.asList(Integer.valueOf( 1 ), Integer.valueOf( 1000 ));

System.out.println(foo.get(0) == bar.get(0)); // true
System.out.println(foo.get(1) == bar.get(1)); // false

Быть более явным, если вы хотите определенного поведения:

final List<Integer> foo =
  Arrays.asList( new Integer( 1 ), new Integer( 1000 ));
final List<Integer> bar =
  Arrays.asList( new Integer( 1 ), new Integer( 1000 ));

System.out.println(foo.get(0) == bar.get(0)); // false
System.out.println(foo.get(1) == bar.get(1)); // false

Это причина, по которой Eclipse имеет автобоксинг как предупреждение по умолчанию.

Ответ 5

У многих людей есть проблемы с этой проблемой, даже люди, которые пишут книги о Java.

В Pro Java Programming, всего лишь несколько дюймов ниже, автор рассказывал о проблемах с использованием автоматических бокс-целых в качестве ключа в IdentityHashMap, он использует автоматические клавиши Integer в формате WeakHashMap. Используемые им значения превышают 128, поэтому его вызов коллекции мусора завершается успешно. Если бы кто-то использовал свой пример и использовал значения, меньшие 128, то его пример не удался (из-за того, что ключ был перма-кэширован).

Ответ 6

Когда вы пишете

foo.get(0)

компилятор не имеет значения, как вы создали список. Он смотрит только на тип компиляции List foo. Итак, если это List <Integer> , он будет рассматривать это как List <Integer> , как он должен делать, а List <Integer> get() всегда возвращает Integer. Если вы хотите использовать ==, тогда вам нужно написать

System.out.println(foo.get(0).intValue() == bar.get(0).intValue());

не

System.out.println(foo.get(0) == bar.get(0));

потому что это имеет совершенно другой смысл.