59 votes

Différence de comportement de l'opérateur ternaire sur JDK8 et JDK10

Considérons le code suivant

public class JDK10Test {
    public static void main(String[] args) {
        Double d = false ? 1.0 : new HashMap<String, Double>().get("1");
        System.out.println(d);
    }
}

Lors de l'exécution sur JDK8, ce code imprime null alors que sur JDK10 ce code entraîne NullPointerException

Exception in thread "main" java.lang.NullPointerException
    at JDK10Test.main(JDK10Test.java:5)

Le bytecode produit par les compilateurs est presque identique à l'exception de deux instructions supplémentaires produits par le JDK10 compilateur qui sont liées à l'autoboxing et semblent être responsables pour les entrées en phase nationale.

15: invokevirtual #7                  // Method java/lang/Double.doubleValue:()D
18: invokestatic  #8                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;

Ce comportement est-il un bug dans JDK10 ou d'une modification intentionnelle de rendre le comportement plus strictes?

JDK8:  java version "1.8.0_172"
JDK10: java version "10.0.1" 2018-04-17

49voto

Radiodef Points 7837

Je crois que c'était un bug qui semble avoir été corrigé. Jetant un NullPointerException semble être le comportement, en fonction de la JLS.

Je pense que ce qui se passe ici est que, pour une raison quelconque dans la version 8, le compilateur considère les limites de la variable type mentionné par le retour de la méthode de type plutôt que sur le type d'arguments. En d'autres termes, il pense qu' ...get("1") retours Object. Ce pourrait être parce que c'est compte tenu de la méthode d'effacement, ou quelque autre raison.

Le comportement devrait s'appuyer sur le type de retour de la get méthode, comme spécifié ci-dessous des extraits d' §15.26:

  • Si le deuxième et le troisième opérande expressions numériques expressions, l'expression conditionnelle est un numérique, expression conditionnelle.

    Pour le classement sous condition, les expressions suivantes sont des expressions numériques:

    • [...]

    • Une invocation de méthode d'expression (§15.12) pour qui la choisi le plus spécifique de la méthode (§15.12.2.5) a un type de retour qui est convertible en un type numérique.

      Notez que, pour une méthode générique, c'est le type avant de l'instanciation de la méthode du type des arguments.

    • [...]

  • Sinon, l'expression conditionnelle est une référence de l'expression conditionnelle.

[...]

Le type de numérique à l'expression conditionnelle est déterminé comme suit:

  • [...]

  • Si l'un des deuxième et troisième opérandes est de type primitif T, et le type de l'autre est le résultat de l'application de la boxe de conversion (§5.1.7) à l' T, alors le type de l'expression conditionnelle est - T.

En d'autres termes, si les deux expressions sont convertibles à un type numérique, et l'un est primitif et l'autre est enfermé dans une boîte, puis le type de résultat de l'expression conditionnelle est le type de primitive.

(Tableau de 15,25 C aussi idéalement nous montre que le type d'une expression ternaire boolean ? double : Double serait en effet double, nouveau sens unboxing et les vomissements est correct).

Si le type de retour de la get méthode n'était pas convertible à un type numérique, puis le ternaire conditionnelle serait considéré comme une "référence de l'expression conditionnelle" et unboxing ne pas se produire.

Aussi, je pense que la note "pour une méthode générique, c'est le type avant l'instanciation de la méthode des arguments de type" ne devrait pas s'appliquer à notre cas. Map.get ne pas déclarer des variables de type, il n'est donc pas une méthode générique par JL' définition. Toutefois, la présente note a été ajoutée dans Java 9 (étant le seul changement, voir JLS8), il est donc possible qu'il ait quelque chose à voir avec le comportement que nous observons aujourd'hui.

Pour un HashMap<String, Double>, le type de retour d' get devrait être Double.

Voici un MCVE l'appui de ma théorie que le compilateur est en considérant le type de la variable limites plutôt que sur le type d'arguments:

class Example<N extends Number, D extends Double> {
    N nullAsNumber() { return null; }
    D nullAsDouble() { return null; }

    public static void main(String[] args) {
        Example<Double, Double> e = new Example<>();

        try {
            Double a = false ? 0.0 : e.nullAsNumber();
            System.out.printf("a == %f%n", a);
            Double b = false ? 0.0 : e.nullAsDouble();
            System.out.printf("b == %f%n", b);

        } catch (NullPointerException x) {
            System.out.println(x);
        }
    }
}

La sortie de ce programme sur Java 8 est:

a == null
java.lang.NullPointerException

En d'autres termes, malgré e.nullAsNumber() et e.nullAsDouble() ayant le même type de retour, seulement e.nullAsDouble() est considéré comme une "expression numérique". La seule différence entre les méthodes est le type de variable liée.

Il y a probablement plus d'enquête qui pourrait être fait, mais je voulais poster mes résultats. J'ai essayé pas mal de choses et a constaté que le bug (c'est à dire pas unboxing/NPE) semble se produire uniquement lorsque l'expression est une méthode avec un type de variable dans le type de retour.


Fait intéressant, j'ai trouvé que le programme suivant met aussi en Java 8:

import java.util.*;

class Example {
    static void accept(Double d) {}

    public static void main(String[] args) {
        accept(false ? 1.0 : new HashMap<String, Double>().get("1"));
    }
}

Qui montre que le compilateur du comportement est en fait différent, selon que le ternaire expression est affectée à une variable locale ou un paramètre d'une méthode.

(À l'origine je voulais utiliser les surcharges de prouver le type réel que le compilateur est de donner à l'expression de l'expression, mais il ne ressemble pas à ce qui est possible compte tenu de la différence susmentionnée. Il est possible, il ya encore une autre façon que je n'ai pas pensé, cependant).

14voto

Jacob G. Points 16099

JLS 10 ne semble pas spécifier toutes les modifications à l'opérateur conditionnel, mais j'ai une théorie.

Selon JLS 8 et JLS 10, si la seconde expression (1.0) est de type double et le troisième (new HashMap<String, Double>().get("1")) est de type Double, alors le résultat de l'expression conditionnelle est de type double. La JVM de Java 8 semble être assez intelligent pour savoir que, parce que vous êtes de retour d'un Double, il n'y a pas de raison de le premier unbox le résultat d' HashMap#get d'un double , puis la boîte de revenir à un Double (parce que vous avez spécifié Double).

Pour prouver cela, modifiez Double de double dans votre exemple, et un NullPointerException est levée (dans le JDK 8); c'est parce que l'unboxing est maintenant produit, et null.doubleValue() évidemment jette un NullPointerException.

double d = false ? 1.0 : new HashMap<String, Double>().get("1");
System.out.println(d); // Throws a NullPointerException

Il semble que ce qui a changé en 10, mais je ne peux pas vous dire pourquoi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X