Тернарный оператор Java против 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...конструкции в этом контексте?

jddxf
источник

Ответы:

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.

dhke
источник
1
Тогда как код, скомпилированный с JDK 1.8, может работать на JDK 1.7. Я знал, что код, скомпилированный с помощью JDK более низкой версии, может без проблем работать на JDK более высокой версии.
jddxf
1
@jddxf Все в порядке, если вы указали правильную версию файла класса и не используете какие-либо функции, недоступные в более поздних версиях. Проблема неизбежна, но если такое использование происходит неявно, как в этом случае.
dhke
13
@jddxf, используйте параметры javac -source / -target
Тагир Валеев
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.

Трудно понять, почему нельзя было написать гипс.

Маркиз Лорн
источник