Почему javac допускает некоторые невозможные приведения, а другие нет? - PullRequest
52 голосов
/ 22 марта 2020

Если я пытаюсь привести String к java.util.Date, компилятор Java улавливает ошибку. Так почему же компилятор не помечает следующее как ошибку?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Конечно, JVM выдает ClassCastException во время выполнения, но компилятор не помечает его.

Поведение такое же, как у javac 1.8.0_212 и 11.0.2.

Ответы [ 3 ]

86 голосов
/ 22 марта 2020

Литейный состав технически возможен . javac не может быть легко доказано, что это не так в вашем случае, и JLS фактически определяет это как действительную Java программу, поэтому пометка ошибки будет неправильной.

Это потому что List это интерфейс. Таким образом, вы могли бы иметь подкласс Date, который фактически реализует List, замаскированный здесь как List, и затем приведение его к Date было бы совершенно нормально. Например:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

И затем:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

Обнаружение такого сценария может быть не всегда возможным, так как для этого потребуется информация времени выполнения, например, если экземпляр поступает из метод вместо. И даже если это потребует гораздо больше усилий для компилятора. Компилятор только предотвращает приведения, которые абсолютно невозможны из-за невозможности сопоставления дерева классов. Это не тот случай, как видно.

Обратите внимание, что JLS требует, чтобы ваш код был действительной Java программой. В 5.1.6.1. Допустимое сужающее ссылочное преобразование говорит:

Существует сужающее ссылочное преобразование из ссылочного типа S в ссылочный тип T, если все из следующих true :

  • [...]
  • Один из следующих случаев применяется :
    • [...]
    • S - это тип интерфейса, T - это тип класса, а T не называет final класс.

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

Было бы разрешено показывать только предупреждение.

15 голосов
/ 22 марта 2020

Давайте рассмотрим обобщение вашего примера:

List<String> strList = someMethod();       
Date d = (Date) strList;

Это основные причины, по которым Date d = (Date) strList; не является ошибкой компиляции.

  • The Интуитивная причина заключается в том, что компилятор (в общем) не знает точный тип объекта, возвращаемого этим вызовом метода. Возможно, что в дополнение к классу, который реализует List, он также также подкласс Date.

  • Техническая причина заключается в том, что Java Language Specification "разрешает" сужающее эталонное преобразование , которое соответствует приведению этого типа. Согласно JLS 5.1.6.1 :

    "Существует сужающее эталонное преобразование из ссылочного типа S в ссылочный тип T, если выполняются все следующие условия : "

    ...

    5) " S - это тип интерфейса, T - это тип класса, а T не называет final class. "

    ...

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

    Обратите внимание, что определение JLS 5.1.6.1 основано исключительно на объявленных типах задействованных переменных, а не на фактических типах времени выполнения. В общем случае компилятор не знает и не может знать фактические типы времени выполнения.


Итак, почему компилятор Java не может решить, что приведение не будет работать?

  • В моем Например, вызов someMethod может возвращать объекты различных типов. Даже если компилятор смог проанализировать тело метода и определить точный набор типов, которые могут быть возвращены, ничто не помешает кому-то изменить его, чтобы он возвращал разные типы ... после компиляции кода, который его вызывает. Это основная причина c, почему JLS 5.1.6.1 говорит то, что говорит.

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

Так почему умный компилятор не может сказать, что это ошибка в любом случае?

  • Поскольку JLS говорит, что это действительная программа. Период. Любой компилятор, который назвал это error , не будет Java совместимым.

  • Кроме того, любой компилятор, который отклоняет Java программы, допустимые JLS и другими компиляторами, препятствует переносимости исходного кода Java. .

2 голосов
/ 27 марта 2020

5.5.1. Приведение типа ссылки:

Учитывая тип ссылки времени компиляции S (источник) и тип ссылки времени компиляции T (цель), преобразование приведения существует с S до T, если нет ошибок времени компиляции из-за следующих правил.

[...]

Если S является типом интерфейса:

  • [...]

  • Если T является классом или типом интерфейса, который не является окончательным, то если существует супертип X из T и супертип Y из S, такой что X и Y являются доказуемо различаемыми параметризованными типами и что стирания X и Y одинаковы, ошибка времени компиляции происходит.

    В противном случае приведение всегда допустимо во время компиляции (потому что даже если T не реализует S, подкласс T может).

List<String> равно S, а Date равно T в вашем случае.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...