Чому javac допускає деякі неможливі ролі, а не інші?


52

Якщо я спробую передати " Stringa" java.util.Date, компілятор Java виявляє помилку. То чому компілятор не позначає таке як помилку?

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

Звичайно, JVM кидає час ClassCastExceptionвиконання, але компілятор не позначає це.

Поведінка однакова з javac 1.8.0_212 та 11.0.2.


2
Нічого особливого Listтут немає. Date d = (Date) new Object();
Елліот Фріш

1
Я граю з ардуїно останнім часом. Мені подобається компілятор, який не з радістю прийняв жоден акторський склад, а потім просто зробив їх із абсолютно непередбачуваними результатами. Рядок до цілого числа? Певна річ! Подвійний на ціле число? Так, сер! Рядок булевий? Принаймні, той з них в основному стає хибним ...
Стіан Іттервік

@ElliottFrisch: Існує очевидна спадкова залежність між Датою та Об'єктом, але між Датою та Списком немає зв’язку. Тож я очікував, що компілятор позначить цей акторський склад таким же чином, як і прапорця від String до Date. Але як пояснює Zabuza у своїй чудовій відповіді, List - це інтерфейс, тому акторський склад був би законним, якби strListбув екземпляр класу, який реалізує List.
Майк Воїноскі

Це питання, що часто повторюється, і я впевнений, що я бачив безліч дублікатів. Це в основному зворотна версія сильно пов'язаного: stackoverflow.com/questions/21812289/…
Халк

1
@StianYttervik -властивий, що це робить. Увімкніть попередження компілятора.
bobsburner

Відповіді:


86

Акторська це технічно можливо. 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якщо всі наведені нижче дії є істинними :

  • [...]
  • Застосовується один із таких випадків :
    • [...]
    • S- це тип інтерфейсу, тип Tкласу і Tне називає finalклас.

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

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


16
І варто зазначити, що причина, по якій він сприймає випадок із String, полягає в тому, що String є остаточним, тому компілятор знає, що жоден клас не може поширити його.
MTilsted

5
Насправді, я не думаю, що саме «остаточність» струни робить myDate = (Date) myStringневдачею. Використовуючи термінологію JLS, оператор намагається перетворити з S(the String) в T(the Date). Тут Sне є тип інтерфейсу, тому умова JLS, цитована вище, не застосовується. Як приклад, спробуйте ввести Календар до дати, і ви отримаєте помилку компілятора, хоча жоден клас не є остаточним.
Майк Воїноскі

1
Я не знаю, розчаруватися чи ні, компілятор не може зробити достатньо статичного аналізу, щоб довести, що strList може коли-небудь мати тип ArrayList.
Джошуа

3
Компілятору не заборонено перевіряти. Але заборонено називати це помилкою. Це зробило б компілятор невідповідним. (Дивіться мою відповідь ...)
Стівен C

3
Для того, щоб додати трохи жаргон, компілятор повинен був би довести , що цей тип Date & Listє непридатним для проживання , це не досить , щоб довести , що він безлюдний в даний час (це може бути в майбутньому).
Полігном

15

Розглянемо узагальнення вашого прикладу:

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

Це основні причини, чому Date d = (Date) strList;не є помилка компіляції.

  • Інтуїтивна причина в тому , що компілятор (в цілому) знати точний тип об'єкта , що повертається цим методом виклику. Можливо, що крім класу, який реалізує List, він також є підкласом Date.

  • Технічна причина в тому , що специфікація мови Java «дозволяє» Звуження Посилальне перетворення , яке відповідає цьому типу акторів. Відповідно до JLS 5.1.6.1 :

    "Перетворення посилань на звуження існує від типу посилання Sдо типу посилання, Tякщо всі наведені нижче дії істинні:"

    ...

    5) " Sтип інтерфейсу, Tце клас класу і Tне називає finalклас."

    ...

    В іншому місці JLS також каже, що виняток може бути кинутий під час виконання ...

    Зауважте, що визначення JLS 5.1.6.1 базується виключно на оголошених типах змінних, а не на фактичних типах виконання. У загальному випадку компілятор не може і не може знати фактичні типи виконання.


Отже, чому компілятор Java не може розібратися, що акторський склад не працюватиме?

  • У моєму прикладі someMethodвиклик може повертати об'єкти з різними типами. Навіть якщо компілятор зміг проаналізувати тіло методу та визначити точний набір типів, які можна було б повернути, нічого не можна зупинити, коли хтось змінить його, щоб повернути різні типи ... після складання коду, який його викликає. Це основна причина, чому JLS 5.1.6.1 говорить те, що говорить.

  • У вашому прикладі розумний компілятор може зрозуміти, що акторський склад не може досягти успіху. І дозволено видавати попередження про час компіляції, щоб вказати на проблему.

Так чому розумному компілятору не дозволяється говорити, що це все-таки помилка?

  • Тому що JLS говорить, що це дійсна програма. Період. Будь-який компілятор, який назвав це помилкою , не відповідав би Java.

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


4
Обґрунтуйте той факт, що після компіляції класу виклику реалізація викликаної функції може змінитися , тому навіть якщо це дозволено під час компіляції, при поточній реалізації виклику, що передача неможлива, це може бути не так у наступні періоди виконання коли виклик змінився або замінився.
Пітер - Відновіть Моніку

2
Оновлення для висвітлення проблеми переносимості, яке було б введено, якщо компілятор намагається бути занадто розумним.
Майк Воїноскі

2

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у вашому випадку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.