Приведение Java: неправильный ли компилятор, или неверная спецификация языка, или я ошибаюсь?

Я читал спецификацию языка Java, 3-е издание, и нашел то, что я считаю несоответствием между спецификацией и реализацией компилятора javac. Те же несоответствия существуют и в компиляторе Eclipse.

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

Это ошибка времени компиляции, если тип операнда времени компиляции никогда не может быть приведен к типу, указанному оператором приведения в соответствии с правилами преобразования приведения (§5.5). В противном случае во время выполнения значение операнда преобразуется (при необходимости) путем приведения преобразования к типу, указанному оператором приведения.

В разделе 5.5 рассказывается о преобразовании приведения. Он дает список типов преобразования, которые разрешены. В частности, в списке отсутствует «преобразование распаковки с последующим расширением / сужением примитивного преобразования». Однако эта точная последовательность преобразований, похоже, разрешена компилятором javac (а также компилятором Eclipse). Например:

long l = (long) Integer.valueOf(45);

... компилируется просто отлично. (Проблемным приведением является приведение к long; аргумент имеет тип java.lang.Integer, поэтому преобразование требует распаковки в int с последующим расширением примитивного преобразования).

Точно так же согласно JLS не должно быть возможности приведения от byte к char, поскольку это (согласно 5.1.4) требует расширяющего примитивного преобразования и сужающего примитивного преобразования, однако это приведение также разрешено компиляторами.

Кто-нибудь может просветить меня?

Редактировать: с тех пор как я задал этот вопрос, я подал отчет об ошибке. с Oracle. Их ответ заключается в том, что это «сбой в JLS».


person davmac    schedule 22.03.2011    source источник
comment
Лучшим примером будет long l = (long) Integer.valueOf(45);   -  person Paŭlo Ebermann    schedule 22.03.2011
comment
Спасибо за предложение, я отредактировал вопрос.   -  person davmac    schedule 22.03.2011


Ответы (2)


Я думаю, что вы правы, компиляторы правы, а спецификация неверна....

Это компилируется: (Object)45 а это нет: (Long)45

Единственный способ понять поведение компиляторов (включая Intellij, который я использую) - это изменить преобразование приведения в соответствие с преобразованием присваивания и преобразованием вызова метода:

  • преобразование бокса (§5.1.7), за которым необязательно следует преобразование расширения ссылки

  • распаковывающее преобразование (§5.1.8), за которым необязательно следует расширяющее примитивное преобразование.

плюс

  • расширение и сужение примитивной конверсии

В спецификации говорилось, что «преобразования приведения являются более инклюзивными, чем преобразования присваивания или вызова метода: приведение может выполнять любое разрешенное преобразование, кроме преобразования строки или преобразования захвата».

person irreputable    schedule 22.03.2011
comment
(Object)45 это просто операция по боксу, верно? (Long)45L работает? я думаю, что (Long)45 технически это две вещи: бокс, а затем преобразование в Long, что не работает, потому что 45 будет упаковано в Integer, а не в Long. - person John Gardner; 22.03.2011
comment
(Object)45 — это преобразование упаковки, за которым следует преобразование расширения ссылки, допустимое в соответствии с предлагаемыми правилами. (Long)45 мог бы работать, если бы мы разрешили комбинацию правил, т. е. расширение примитива + упаковка - person irreputable; 22.03.2011
comment
Я думаю ты прав. Я заметил, что в вызове метода и преобразовании присваивания конкретно указано, что вы можете использовать только одну из перечисленных последовательностей преобразования, в то время как преобразование приведения конкретно не указывает, что может использоваться только один: произвольный выбор позволил бы, например, ненужное примитивное сужающее преобразование (с последующим расширением), которое могло бы изменить значение, в случае, когда преобразования тождества было бы достаточно. - person davmac; 22.03.2011
comment
(Кроме того, разрешение произвольного выбора, конечно, позволит использовать ваш пример (Long)45). - person davmac; 22.03.2011
comment
JLS не самого высокого качества. Довольно грязно во многих местах. - person irreputable; 22.03.2011

Насколько я читал, приведение от int к long разрешено этим пунктом:

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

Преобразование int в long является расширяющим примитивным преобразованием.

Это просто оставляет преобразование из Integer в int, что соответствует последнему пункту:

преобразование распаковки

Конечно, приведение к long в этом примере даже не нужно.

Рассмотрим следующие четыре определения:

final Integer io = Integer.valueOf(45);
final int i = io;
final long l1 = (long)i;
final long l2 = i;

Считаете ли вы кого-то из них удивительным? Ваш исходный пример ничем не отличается; он просто исключает промежуточные переменные.

person seh    schedule 22.03.2011
comment
Вы подразумеваете, что все преобразования, перечисленные в 5.5, могут применяться по желанию и в любом порядке? (порядок, требуемый в моем примере, заключается в том, чтобы сначала произошло преобразование распаковки, а затем расширение). Это делает перечисление преобразования идентификаторов излишним, а также делает неясным, почему непроверенное преобразование имеет особый случай (путем его объединения с преобразованиями расширения/сужения ссылки). Я совершенно уверен, что только одна из перечисленных конверсий может быть применена. - person davmac; 22.03.2011
comment
Хотя: я отмечаю, что преобразование приведения конкретно не говорит о том, что может применяться только одна из перечисленных последовательностей преобразования, как это делают преобразование вызова метода и преобразование присваивания. - person davmac; 22.03.2011
comment
На самом деле подробные правила в 5.5 прямо говорят, что вся процедура в некоторых случаях применяется рекурсивно. Это говорит мне о том, что правила наверняка могут быть применены более одного раза. - person Ted Hopp; 22.03.2011
comment
@ Тед, можешь дать мне точное место/текст этого? Самое близкое, что я могу найти, это: если T является переменной типа, то этот алгоритм применяется рекурсивно, используя верхнюю границу T вместо T, но это применимо только для приведения от одного ссылочного типа к другому. - person davmac; 22.03.2011