Термінальний оператор Java vs if / else у сумісність <JDK8


113

Нещодавно я читаю вихідний код Spring Framework. Щось я не можу зрозуміти, йде тут:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

Цей метод є членом класу org.springframework.core.MethodParameter. Код легко зрозуміти, поки коментарі важкі.

ПРИМІТКА: немає потрійного вираження для збереження сумісності JDK <8 навіть при використанні компілятора JDK 8 (потенційно вибирається java.lang.reflect.Executableяк загальний тип, при цьому новий базовий клас недоступний для старих JDK)

Яка різниця між використанням потрійного виразу та використанням if...else...конструкції в цьому контексті?

Відповіді:


103

Коли ви думаєте про тип операндів, проблема стає більш очевидною:

this.method != null ? this.method : this.constructor

має як тип найбільш спеціалізований загальний тип обох операндів, тобто найбільш спеціалізований тип, спільний для обох this.methodта this.constructor.

У Java 7 це java.lang.reflect.Member, однак бібліотека класів Java 8 представляє новий тип, java.lang.reflect.Executableякий є більш спеціалізованим, ніж загальний Member. Отже, з бібліотекою класів Java 8 тип результату потрійного виразу є, Executableа не Member.

Деякі (до випуску) версії компілятора Java 8, здається, створили чітке посилання на Executableвсередині згенерованого коду під час компіляції потрійного оператора. Це призведе до завантаження класу, і, в свою чергу, ClassNotFoundExceptionпід час виконання під час роботи з бібліотекою класів <JDK 8, оскільки Executableіснує лише для JDK ≥ 8.

Як зазначає Тагір Валєєв у цій відповіді , це фактично помилка у попередньо випущених версіях JDK 8 і з тих пір виправлена, тож як if-elseвирішення, так і пояснювальний коментар застаріли.

Додаткова примітка: Можна прийти до висновку, що ця помилка компілятора була присутньою перед Java 8. Однак код байта, сформований для потрійного OpenJDK 7, такий же, як і байт-код, сформований OpenJDK 8. Фактично, тип Вираз стає повністю невідомим під час виконання, код - це справді лише тест, гілка, завантаження, повернення без додаткових перевірок. Тож будьте впевнені, що це вже не проблема (і справді, здається, це була тимчасова проблема під час розробки Java 8.


1
Тоді як код, складений з JDK 1.8, може працювати на JDK 1.7. Я знав, що код, скомпільований з нижчою версією JDK, може працювати без проблем на більш високій версії JDK.
jddxf

1
@jddxf Все добре, якщо ви вказали належну версію файлу класу та не використовуєте жодних функціональних можливостей, недоступних у пізніших версіях. Проблема може виникнути, однак якщо таке використання відбувається неявно, як у цьому випадку.
dhke

13
@jddxf, використання -source / -target варіанти JAVAC
Тагір Валєєв

1
Дякую всім, особливо dhke та Тагіру Валєєву, які дали ґрунтовне пояснення
jddxf

30

Це було внесено в досить старий комітет 3 травня 2013 року, майже за рік до офіційного виходу JDK-8. У той час компілятор був у важкому стані, тому такі проблеми сумісності можуть виникнути. Я здогадуюсь, команда Spring щойно перевірила збірку JDK-8 і намагалася виправити проблеми, хоча вони насправді є проблемами компілятора. За офіційним випуском JDK-8 це стало неактуальним. Тепер потрійний оператор у цьому коді працює нормально, як очікувалося (ніякого посилання на Executableклас у зібраному .class-файлі немає).

В даний час подібні речі з'являються в JDK-9: деякий код, який можна добре скласти в JDK-8, не вдався до JDK-9 javac. Я думаю, більшість таких проблем будуть виправлені до випуску.


2
+1. Отже, це була помилка у ранньому компіляторі? Чи поведінка, про яку йдеться Executable, порушувала деякий аспект специфікації? Або це просто те, що Oracle зрозумів, що вони можуть змінити таку поведінку таким чином, що все-таки відповідатимуть специфікації та не порушуючи зворотної сумісності?
ruakh

2
@ruakh, я думаю, це була помилка. У байт-коді (або в Java-8, або в попередньому) явно не потрібно чітко додавати Executableвводити між ними. У Java-8 концепція виводу типу вираження різко змінилася, і ця частина була повністю переписана, тому не так дивно, що у ранніх впровадженнях були помилки.
Тагір Валєєв

7

Основна відмінність полягає в тому, що if elseблок є оператором, тоді як потрійний (частіше відомий як умовний оператор на Java) є виразом .

Заява може зробити такі речі , як returnдо викликає на деяких з каналів управління. Вираз може бути використано в призначенні:

int n = condition ? 3 : 2;

Тож два вирази у трійці після умови потрібно прирівняти до одного типу. Це може спричинити деякі дивні ефекти у Java, особливо при автоматичному боксі та автоматичному відправці посилань - саме про це йдеться у коментарі до вашого опублікованого коду. Примус виразів у вашому випадку полягає у java.lang.reflect.Executableтипі (як це найбільш спеціалізований тип ) і такого не існує у старих версіях Java.

Стилістично ви повинні використовувати if elseблок, якщо код схожий на заяву, і потрійний, якщо він схожий на вираз.

Звичайно, ви можете змусити if elseблок поводитись як вираз, якщо ви використовуєте функцію лямбда.


6

На тип повернутого значення в потрійному виразі впливають батьківські класи, які змінилися, як описано в Java 8.

Важко зрозуміти, чому не може бути написаний акторський склад.

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