web-dev-qa-db-fra.com

Renvoyer null comme un int autorisé avec l'opérateur ternaire, mais pas avec l'instruction if

Regardons le code Java simple dans l'extrait suivant:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

Dans ce code Java le plus simple, la méthode temp() ne génère aucune erreur de compilation, même si le type de retour de la fonction est int et nous essayons de renvoyer la valeur null (via l'instruction return true ? null : 0;). Une fois compilé, ceci provoque évidemment l’exception d’exécution NullPointerException

Cependant, il semble que la même chose soit fausse si nous représentons l'opérateur ternaire avec une instruction if (comme dans la méthode same()), qui fait émet une erreur lors de la compilation! Pourquoi?

185
Lion

Le compilateur interprète null comme une référence null à Integer, applique les règles de sélection automatique/unboxing pour l'opérateur conditionnel (comme décrit dans la spécification de langage Java, 15.25 ), et poursuit son chemin avec bonheur. Cela générera une NullPointerException au moment de l’exécution, que vous pourrez confirmer en l’essayant.

115
Ted Hopp

Je pense que le compilateur Java interprète true ? null : 0 comme une expression Integer, qui peut être implicitement convertie en int, donnant éventuellement NullPointerException.

Dans le second cas, l'expression null est du type spécial nullsee , de sorte que le code return null rend le type incompatible.

39
Vlad

En fait, tout cela est expliqué dans la spécification de langage Java .

Le type d'une expression conditionnelle est déterminé comme suit:

  • Si les deuxième et troisième opérandes ont le même type (qui peut être le type null), il s'agit du type de l'expression conditionnelle.

Par conséquent, le "null" dans votre (true ? null : 0) obtient un type int, puis est automatiquement remplacé par Integer. 

Essayez quelque chose comme ceci pour vérifier ce (true ? null : null) et vous obtiendrez l'erreur du compilateur.

32
nowaq

Dans le cas de l'instruction if, la référence null n'est pas traitée comme une référence Integer car elle ne participe pas à une expression qui l'oblige à être interprétée en tant que telle. Par conséquent, l'erreur peut être facilement interceptée au moment de la compilation car il s'agit plus clairement d'une erreur type.

En ce qui concerne l'opérateur conditionnel, la spécification du langage Java, § 15.25 «Opérateur conditionnel ? :», répond parfaitement aux règles d'application de la conversion de type:

  • Si les deuxième et troisième opérandes ont le même type (qui peut être le type null ), Il s'agit du type de l'expression conditionnelle.

    Ne s'applique pas car null n'est pas int.

  • Si l'un des deuxième et troisième opérandes est de type booléen et le type de Si l'autre est de type booléen, le type de l'expression conditionnelle est boolean.

    Ne s'applique pas parce que ni null ni int n'est ni boolean ni Boolean.

  • Si l'un des deuxième et troisième opérandes est du type null et du type du autre est un type de référence, le type de l'expression conditionnelle est donc Type de référence.

    Ne s'applique pas car null est du type null, mais int n'est pas un type de référence.

  • Sinon, si les deuxième et troisième opérandes ont des types convertibles (§5.1.8) aux types numériques, alors il y a plusieurs cas: […]

    S'applique: null est traité comme convertible en un type numérique et est défini au § 5.1.8 “Conversion d'unboxing” pour générer une NullPointerException.
25
Jon Purdy

La première chose à garder à l’esprit est que les opérateurs ternaires Java ont un "type" et que c’est ce que le compilateur déterminera et considérera, quels que soient les types réels/réels du deuxième ou du troisième paramètre. En fonction de plusieurs facteurs, le type d'opérateur ternaire est déterminé de différentes manières, comme illustré dans la Spécification du langage Java 15.26

Dans la question ci-dessus, nous devrions considérer le dernier cas:

Sinon, les deuxième et troisième opérandes sont de types S1 _ et S2 respectivement. Soit T1 le type résultant de l'application de la conversion de boxe en S1, et soit T2 le type résultant de l'application de la conversion de la boxe en S2. Le type de l'expression conditionnelle est le résultat de l'application de la conversion de capture (§5.1.10) à lub (T1, T2)} (§15.12.2.7).

C’est de loin le cas le plus complexe lorsque vous examinez l’application de la conversion de capture (§5.1.10) et surtout de lub (T1, T2).

En clair et après une simplification extrême, nous pouvons décrire le processus comme le calcul de la "classe la moins commune (Super Least Common Superclass") (oui, pensez au LCM) des deuxième et troisième paramètres. Cela nous donnera l'opérateur "type" ternaire. Encore une fois, ce que je viens de dire est une simplification extrême (considérons les classes qui implémentent plusieurs interfaces communes).

Par exemple, si vous essayez ce qui suit:

long millis = System.currentTimeMillis();
return(true ? new Java.sql.Timestamp(millis) : new Java.sql.Time(millis));

Vous remarquerez que le type résultant de l'expression conditionnelle est Java.util.Date puisqu'il s'agit de la "superclasse la moins commune" de la paire TimestampTime.

Étant donné que null peut être associé à n'importe quoi, la "classe la moins commune" est la classe Integer et il s'agira du type de retour de l'expression conditionnelle (opérateur ternaire) ci-dessus. La valeur de retour sera alors un pointeur null de type Integer et c'est ce qui sera retourné par l'opérateur ternaire.

Au moment de l'exécution, lorsque la machine virtuelle Java déballe le Integer, un NullPointerException est émis. Cela est dû au fait que la machine virtuelle Java tente d'appeler la fonction null.intValue(), où null est le résultat de l'autoboxing.

À mon avis (et comme je ne suis pas dans la spécification du langage Java, de nombreuses personnes le trouveront mal de toute façon), le compilateur fait un travail médiocre en évaluant l'expression de votre question. Étant donné que vous avez écrit true ? param1 : param2, le compilateur doit déterminer immédiatement que le premier paramètre -null- sera renvoyé et générer une erreur de compilation. Ceci est un peu similaire à lorsque vous écrivez while(true){} etc... et que le compilateur se plaint du code situé sous la boucle et le marque avec Unreachable Statements.

Votre deuxième cas est assez simple et cette réponse est déjà trop longue ...;)

_/CORRECTION:

Après une autre analyse, j’ai eu tort de dire qu’une valeur null peut être encapsulée ou automatiquement bloquée. En parlant de la classe Integer, la boxe explicite consiste à invoquer le constructeur new Integer(...) ou peut-être la Integer.valueOf(int i); (j'ai trouvé cette version quelque part). Le premier lancerait un NumberFormatException (et cela ne se produirait pas) tandis que le second n'aurait tout simplement aucun sens, car un int ne peut pas être null...

11
Gevorg

En fait, dans le premier cas, l'expression peut être évaluée, car le compilateur sait qu'elle doit être évaluée en tant que Integer. Toutefois, dans le second cas, le type de la valeur de retour (null) ne peut pas être déterminé. compilé. Si vous le convertissez en Integer, le code sera compilé.

4
GeT
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of auto-boxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}
2
YouYou

Que dis-tu de ça:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

La sortie est vraie, vraie.

La couleur Eclipse code le 1 dans l'expression conditionnelle comme étant automatiquement sélectionné.

Je suppose que le compilateur voit le type de retour de l'expression sous forme d'objet.

0
Jon