Почему компилятор / JVM не могут просто заставить автобокс работать? - PullRequest
12 голосов
/ 08 апреля 2010

Автобокс довольно страшный. В то время как я полностью понимаю разницу между == и .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 не всегда автоматически распаковывается в примитив?

Печать false false или true true была бы намного лучше.

EDIT

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

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

Ответы [ 6 ]

11 голосов
/ 08 апреля 2010
  • Почему они так поступили?

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

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

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

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

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

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

Вместо этого слишком переживайте по этому поводу, просто запомните это правило:

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

7 голосов
/ 08 апреля 2010

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

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

5 голосов
/ 08 апреля 2010

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

А с здесь

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

4 голосов
/ 08 апреля 2010

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

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 по умолчанию использует автобокс в качестве предупреждения.

3 голосов
/ 09 апреля 2010

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

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

2 голосов
/ 01 июня 2011

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

foo.get(0)

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

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

не

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

потому что это имеет совершенно другое значение.

...