Opérateur ternaire Java vs if / else dans <compatibilité JDK8

113

Récemment, je lis le code source de Spring Framework. Quelque chose que je ne peux pas comprendre va ici:

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;
    }
}

Cette méthode fait partie de la classe org.springframework.core.MethodParameter. Le code est facile à comprendre tandis que les commentaires sont difficiles.

REMARQUE: aucune expression ternaire pour conserver la compatibilité JDK <8 même lors de l'utilisation du compilateur JDK 8 (éventuellement en sélectionnant java.lang.reflect.Executablecomme type commun, avec cette nouvelle classe de base non disponible sur les anciens JDK)

Quelle est la différence entre l'utilisation d'une expression ternaire et l'utilisation d'une if...else...construction dans ce contexte?

jddxf
la source

Réponses:

103

Lorsque vous pensez au type des opérandes, le problème devient plus apparent:

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

a pour type le type commun le plus spécialisé des deux opérandes, c'est-à-dire le type le plus spécialisé commun aux deux this.methodet this.constructor.

Dans Java 7 c'est java.lang.reflect.Member, cependant la bibliothèque de classes Java 8 introduit un nouveau type java.lang.reflect.Executablequi est plus spécialisé que le générique Member. Par conséquent, avec une bibliothèque de classes Java 8, le type de résultat de l'expression ternaire est Executableplutôt que Member.

Certaines versions (pré-version) du compilateur Java 8 semblent avoir produit une référence explicite à Executable code généré intérieur lors de la compilation de l'opérateur ternaire. Cela déclencherait un chargement de classe, et donc à son tour un ClassNotFoundExceptionà l'exécution lors de l'exécution avec une bibliothèque de classes <JDK 8, carExecutable n'existe que pour JDK ≥ 8.

Comme l'a noté Tagir Valeev dans cette réponse , il s'agit en fait d'un bogue dans les versions préliminaires de JDK 8 et a depuis été corrigé, de sorte que leif-else solution de contournement et le commentaire explicatif sont maintenant obsolètes.

Note supplémentaire: On pourrait en venir à la conclusion que ce bogue du compilateur était présent avant Java 8. Cependant, le code d'octet généré pour le ternaire par OpenJDK 7 est le même que le code d'octet généré par OpenJDK 8. En fait, le type du L'expression n'est absolument pas mentionnée au moment de l'exécution, le code n'est en réalité qu'un test, une branche, un chargement, un retour sans aucune vérification supplémentaire. Soyez donc assuré que ce n'est pas (plus) un problème et semble en effet avoir été un problème temporaire lors du développement de Java 8.

dhke
la source
1
Alors, comment le code compilé avec JDK 1.8 peut-il fonctionner sur JDK 1.7. Je savais que le code compilé avec la version inférieure JDK peut fonctionner sans problème sur la version supérieure JDK.
jddxf
1
@jddxf Tout va bien tant que vous avez spécifié la version de fichier de classe appropriée et que vous n'utilisez aucune fonctionnalité qui n'est pas disponible dans les versions ultérieures. Des problèmes sont inévitables, cependant si une telle utilisation se produit implicitement comme dans ce cas.
dhke
13
@jddxf, utilisez les options javac -source / -target
Tagir Valeev
1
Merci à tous, en particulier à Dhke et Tagir Valeev, qui ont donné une explication approfondie
jddxf
30

Cela a été introduit dans un commit assez ancien le 3 mai 2013, presque un an avant la sortie officielle du JDK-8. Le compilateur était en cours de développement intensif à cette époque, de tels problèmes de compatibilité pouvaient donc survenir. Je suppose que l'équipe Spring vient de tester la version JDK-8 et a essayé de résoudre les problèmes, même s'il s'agit en fait de problèmes de compilateur. À la sortie officielle du JDK-8, cela est devenu hors de propos. Maintenant, l'opérateur ternaire dans ce code fonctionne correctement comme prévu (aucune référence àExecutable classe dans le fichier .class compilé n'est présente).

Actuellement, des choses similaires apparaissent dans JDK-9: certains codes qui peuvent être bien compilés dans JDK-8 échouent avec JDK-9 javac. Je suppose que la plupart de ces problèmes seront résolus jusqu'à la sortie.

Tagir Valeev
la source
2
+1. Alors, était-ce un bogue dans le premier compilateur? Est-ce que ce comportement, là où il se référait Executable, violait certains aspects des spécifications? Ou est-ce simplement qu'Oracle s'est rendu compte qu'il pouvait changer ce comportement d'une manière qui serait toujours conforme à la spécification et sans rompre la compatibilité descendante?
ruakh
2
@ruakh, je suppose que c'était le bogue. En bytecode (que ce soit en Java-8 ou antérieur), il est totalement inutile de transtyper explicitement pour Executabletaper entre les deux. En Java-8, le concept d'inférence de type d'expression a radicalement changé et cette partie a été complètement réécrite, il n'est donc pas si surprenant que les premières implémentations aient des bogues.
Tagir Valeev
7

La principale différence est qu'un if elsebloc est une instruction tandis que le ternaire (plus souvent appelé opérateur conditionnel en Java) est une expression .

Une instruction peut faire des choses comme returnà l'appelant sur certains des chemins de contrôle. Une expression peut être utilisée dans une affectation:

int n = condition ? 3 : 2;

Ainsi, les deux expressions dans le ternaire après la condition doivent être forcées au même type. Cela peut provoquer des effets étranges en Java, en particulier avec l'auto-boxing et le casting de référence automatique - c'est à cela que fait référence le commentaire dans votre code publié. La contrainte des expressions dans votre cas serait un java.lang.reflect.Executabletype (car c'est le type le plus spécialisé ) et qui n'existe pas dans les anciennes versions de Java.

Stylistiquement, vous devez utiliser un if elsebloc si le code ressemble à une instruction et un ternaire s'il ressemble à une expression.

Bien sûr, vous pouvez faire en sorte qu'un if elsebloc se comporte comme une expression si vous utilisez une fonction lambda.

Bathsheba
la source
6

Le type de valeur de retour dans une expression ternaire est affecté par les classes parentes, qui ont changé comme décrit dans Java 8.

Difficile de voir pourquoi un casting n'aurait pas pu être écrit.

Marquis de Lorne
la source