Различия в автоматической распаковке между Java 6 и Java 7

107

Я заметил разницу в поведении автоматической распаковки между Java SE 6 и Java SE 7. Мне интересно, почему это так, потому что я не могу найти никакой документации об изменениях в этом поведении между этими двумя версиями.

Вот простой пример:

Object[] objs = new Object[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Это прекрасно компилируется с javac из Java SE 7. Однако, если я передам компилятору аргумент «-source 1.6», я получу ошибку в последней строке:

inconvertible types
found   : java.lang.Object
required: int

Я попытался загрузить Java SE 6 для компиляции с помощью собственного компилятора версии 6 (без параметра -source). Согласен и выдает ту же ошибку, что и выше.

Так что же дает? Из еще нескольких экспериментов кажется, что распаковка в Java 6 может распаковывать только значения, которые явно (во время компиляции) относятся к упакованному типу. Например, это работает в обеих версиях:

Integer[] objs = new Integer[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Таким образом, кажется, что между Java 6 и 7 функция распаковки была улучшена, так что она могла приводить и распаковывать типы объектов одним махом, не зная (во время компиляции), что значение имеет правильный упакованный тип. Однако, читая Спецификацию языка Java или сообщения в блогах, которые были написаны во время выхода Java 7, я не вижу никаких изменений в этой вещи, поэтому мне интересно, что это за изменение и как называется эта «функция» ?

Просто любопытство: из-за изменения возможно запускать "неправильные" распаковки:

Object[] objs = new Float[2];
objs[0] = new Float(5);
int myInt = (int)objs[0];

Это хорошо компилируется, но дает исключение ClassCastException во время выполнения.

Любая ссылка на это?

Морти
источник
17
Интересный. Новый ингредиент для беспорядка автобокса. Я думаю, что ваш пример мог бы быть более простым и понятным с одним объектом вместо массива. Integer obj = new Integer(2); int x = (int)obj;: работает на Java 7, выдает ошибку на Java 6.
leonbloy
1
Какой JDK вы используете? Возможно, это также связано с разными поставщиками ...
barfuin
1
@leonbloy: Хорошее замечание по поводу упрощения, я несколько упростил его (из моего исходного кода), но почему-то остановился слишком рано!
Морти
@Thomas: Я использовал последний JDK (для каждой версии) от Oracle.
Морти
2
Еще одна причина никогда не использовать автобокс.
gyorgyabraham

Ответы:

92

Похоже, что язык в разделе 5.5 Приведение преобразования Java 7 JLS был обновлен по сравнению с тем же разделом в Java 5/6 JLS , вероятно, для уточнения разрешенных преобразований.

Java 7 JLS говорит

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

Java 5/6:

Значение ссылочного типа может быть приведено к примитивному типу путем преобразования распаковки (§5.1.8).

Java 7 JLS также содержит таблицу (таблица 5.1) разрешенных преобразований (эта таблица не включена в Java 5/6 JLS) из ссылочных типов в примитивы. Это явно перечисляет приведение от Object к примитивам как преобразование сужающей ссылки с распаковкой.

Причина объясняется в этом электронном письме :

Итог: Если спец. allow (Object) (int), он также должен разрешать (int) (Object).

Марк Роттевил
источник
35

Ты прав; проще говоря:

Object o = new Integer(1234);
int x = (int) o;

Это работает в Java 7, но дает ошибку компиляции в Java 6 и ниже. Как ни странно, эта функция не задокументирована; например, об этом здесь не упоминается . Спорный вопрос, новая ли это функция или исправление ошибки (или новая ошибка?), См. Некоторую связанную информацию и обсуждение . Консенсус, похоже, указывает на двусмысленность в исходной спецификации, что привело к несколько неправильной / непоследовательной реализации на Java 5/6, которая была исправлена ​​в версии 7, потому что это было критически важно для реализации JSR 292 (динамически типизированные языки).

У автобокса Java теперь есть еще несколько ловушек и сюрпризов. Например

Object obj = new Integer(1234);
long x = (long)obj;

будет компилироваться, но не удастся (с ClassCastException) во время выполнения. Вместо этого будет работать:

long x = (long)(int)obj;

Леонблой
источник
2
Спасибо за ответ. Однако одного я не понимаю. Это разъяснение JLS и сопутствующих реализаций (см. Обсуждение по электронной почте), но почему это должно быть сделано для размещения других типизированных языков в JVM? В конце концов, это изменение языка, а не виртуальной машины: поведение преобразования виртуальной машины работает так же, как и всегда, компилятор реализует эту функцию, используя существующий механизм преобразования в Integer и вызова .intValue (). Так как же это изменение в собственно языке Java может помочь запускать другие языки на виртуальной машине? Я согласен, ваша ссылка предлагает это, просто интересно.
Морти