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

186

Examinons le code Java simple dans l'extrait de code 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 temp()méthode n'émet aucune erreur du compilateur 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é, cela provoque évidemment l'exception d'exécution NullPointerException.

Cependant, il semble que la même chose ne va pas si nous représentons l'opérateur ternaire avec une ifinstruction (comme dans la same()méthode), ce qui génère une erreur de compilation! Pourquoi?

Lion
la source
6
De plus, int foo = (true ? null : 0)et les new Integer(null)deux se compilent bien, le second étant la forme explicite de la boîte automatique.
Izkata
2
@Izkata le problème est pour moi ici pour comprendre pourquoi le compilateur essaie de autobox nullà Integer... Ce serait regarder comme « deviner » pour moi ou « faire des choses travail » ...
Marsellus Wallace
1
... Huhm, je pensais avoir une réponse là-bas, car le constructeur Integer (ce que la documentation que j'ai trouvé dit est utilisé pour l'autoboxing) est autorisé à prendre une chaîne comme argument (qui peut être nul). Cependant, ils disent aussi que le constructeur agit de la même manière que la méthode parseInt (), qui lèverait une NumberFormatException après avoir passé un null ...
Izkata
3
@Izkata - l'argument String c'tor pour Integer n'est pas une opération de recherche automatique. Une chaîne ne peut pas être automatiquement convertie en un entier. (La fonction Integer foo() { return "1"; }ne compile pas.)
Ted Hopp
5
Cool, j'ai appris quelque chose de nouveau sur l'opérateur ternaire!
oksayt

Réponses:

118

Le compilateur interprète nullcomme une référence nulle à an Integer, applique les règles de mise en boîte automatique / unboxing pour l'opérateur conditionnel (comme décrit dans la spécification du langage Java, 15.25 ), et avance avec bonheur. Cela générera un NullPointerExceptionau moment de l'exécution, que vous pouvez confirmer en l'essayant.

Ted Hopp
la source
Compte tenu du lien vers la spécification du langage Java que vous avez publié, quel point pensez-vous être exécuté dans le cas de la question ci-dessus? Le dernier (puisque j'essaye encore de comprendre capture conversionet lub(T1,T2)) ?? Aussi, est-il vraiment possible d'appliquer la boxe à une valeur nulle? Ne serait-ce pas comme "deviner" ??
Marsellus Wallace
´ @ Gevorg Un pointeur nul est un pointeur valide vers tous les objets possibles, donc rien de mauvais ne peut s'y produire. Le compilateur suppose simplement que null est un Integer qu'il peut ensuite effectuer automatiquement en int.
Voo
1
@Gevorg - Voir le commentaire de nowaq et ma réponse à son message. Je pense qu'il a choisi la bonne clause. lub(T1,T2)est le type de référence le plus spécifique commun dans la hiérarchie des types de T1 et T2. (Ils partagent tous les deux au moins Object, il y a donc toujours un type de référence plus spécifique.)
Ted Hopp
8
@Gevorg - nulln'est pas encadré dans un Integer, il est interprété comme une référence à un Integer (une référence nulle, mais ce n'est pas un problème). Aucun objet Integer n'est construit à partir de la valeur null, il n'y a donc aucune raison pour une NumberFormatException.
Ted Hopp
1
@Gevorg - Si vous regardez les règles de conversion de boxe et que vous les appliquez à null(qui n'est pas un type numérique primitif), la clause applicable est "Si p est une valeur d'un autre type, la conversion de boxe équivaut à une conversion d'identité ". Donc, la conversion boxing de nullen Integerrendements null, sans invoquer aucun Integerconstructeur.
Ted Hopp
40

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

Pour le second cas, l'expression nullest du type spécial nul , voir , donc le code return nullcrée une incompatibilité de type.

Vlad
la source
2
Je suppose que cela est lié à la boxe automatique? Vraisemblablement, le premier retour ne serait pas compilé avant Java 5, non?
Michael McGowan
@Michael, cela semble être le cas si vous définissez le niveau de conformité d'Eclipse sur pré-5.
Jonathon Faust
@Michael: cela ressemble vraiment à l'auto-boxing (je suis assez nouveau sur Java, et je ne peux pas faire de déclaration plus définie - désolé).
Vlad
1
@Vlad comment le compilateur finirait-il par interpréter true ? null : 0comme Integer? Par autoboxing d' 0abord ??
Marsellus Wallace
1
@Gevorg: Regardez ici : Sinon, les deuxième et troisième opérandes sont de types S1 et S2 respectivement. Soit T1 le type qui résulte de l'application de la conversion de boxe à S1, et soit T2 le type qui résulte de l'application de la conversion de boxe à S2. et le texte suivant.
Vlad
32

En fait, tout est expliqué dans la spécification du 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 nul), alors c'est le type de l'expression conditionnelle.

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

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

nowaq
la source
3
Mais cette clause des règles ne s'applique pas: les deuxième et troisième opérandes n'ont pas le même type.
Ted Hopp du
1
Alors la réponse semble être dans l'énoncé suivant:> Sinon, les deuxième et troisième opérandes sont de types S1 et S2 respectivement. Soit T1 le type qui résulte de l'application de la conversion de boxe à S1, et soit T2 le type qui résulte de l'application de la conversion de boxe à S2. Le type de l'expression conditionnelle est le résultat de l'application de la conversion de capture (§5.1.10) en lub (T1, T2) (§15.12.2.7).
nowaq
Je pense que c'est la clause applicable. Ensuite, il essaie d'appliquer un déballage automatique afin de renvoyer une intvaleur de la fonction, ce qui provoque un NPE.
Ted Hopp
@nowaq Je pensais cela aussi. Toutefois, si vous essayez de boîte explicitement nullà Integeravec new Integer(null);« Soit T1 du type résultant de l' application de conversion de boxe à S1 ... » vous obtiendrez un NumberFormatExceptionet ce n'est pas le cas ...
Marsellus Wallace
@Gevorg Je pense qu'une exception se produit lors de la boxe, nous n'obtenons AUCUN résultat ici. Le compilateur est simplement obligé de générer du code qui suit la définition qu'il fait - nous obtenons simplement l'exception avant d'avoir terminé.
Voo
25

Dans le cas de l' ifinstruction, la nullréférence n'est pas traitée comme une Integerréférence car elle ne participe pas à une expression qui la force à être interprétée comme telle. Par conséquent, l'erreur peut être facilement détectée au moment de la compilation car il s'agit plus clairement d'une erreur de type .

En ce qui concerne l'opérateur conditionnel, la spécification du langage Java §15.25 «Conditional Operator ? :» répond bien à cela dans les 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 nul), alors c'est le type de l'expression conditionnelle.

    Ne s'applique pas parce que nullnon int.

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

    Ne s'applique pas parce que ni nullni intn'est booleanni Boolean.

  • Si l'un des deuxième et troisième opérandes est de type nul et que le type de l'autre est un type référence, le type de l'expression conditionnelle est ce type référence.

    Ne s'applique pas car il nullest de type nul, mais intn'est pas un type référence.

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

    S'applique: nullest traité comme convertible en type numérique, et est défini au §5.1. 8 «Unboxing Conversion» pour lancer a NullPointerException.
Jon Purdy
la source
Si 0est automatiquement renvoyé, Integerle compilateur exécute le dernier cas des "règles d'opérateur ternaire" comme décrit dans la spécification du langage Java. Si c'est vrai, alors il m'est difficile de croire que cela passerait alors au cas 3 des mêmes règles qui ont un type null et un type de référence qui font que la valeur de retour de l'opérateur ternaire est le type de référence (Integer). .
Marsellus Wallace
@Gevorg - Pourquoi est-il difficile de croire que l'opérateur ternaire renvoie un Integer? C'est exactement ce qui se passe; le NPE est généré en essayant de déballer la valeur de l'expression afin de renvoyer un à intpartir de la fonction. Changez la fonction pour renvoyer un Integeret il reviendra nullsans problème.
Ted Hopp
2
@TedHopp: Gevorg répondait à une révision précédente de ma réponse, qui était incorrecte. Vous devez ignorer l'écart.
Jon Purdy
@JonPurdy "On dit qu'un type est convertible en type numérique s'il s'agit d'un type numérique, ou s'il s'agit d'un type de référence qui peut être converti en type numérique par conversion unboxing" et je ne pense pas que cela nulltombe dans cette catégorie . De plus, nous passerions ensuite à l'étape "Sinon, la promotion numérique binaire (§5.6.2) est appliquée ... Notez que la promotion numérique binaire effectue la conversion de déballage (§5.1.8) ..." pour déterminer le type de retour. Mais la conversion unboxing générerait un NPE et cela ne se produit qu'au moment de l'exécution et non en essayant de déterminer le type d'opérateur ternaire. Je suis toujours confus ..
Marsellus Wallace
@Gevorg: le déballage se produit au moment de l'exécution. Le nullest traité comme s'il avait du type int, mais est en fait équivalent à throw new NullPointerException(), c'est tout.
Jon Purdy
11

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 respectivement de types S1 et S2 . Soit T1 le type qui résulte de l'application de la conversion de boxe à S1 , et soit T2 le type qui résulte de l'application de la conversion de boxe à S2 . Le type de l'expression conditionnelle est le résultat de l'application de la conversion de capture (§5.1.10) en lub (T1, T2) (§15.12.2.7).

C'est de loin le cas le plus complexe une fois que vous regardez l' application de la conversion de capture (§5.1.10) et surtout à lub (T1, T2) .

En anglais simple et après une simplification extrême, nous pouvons décrire le processus comme le calcul de la "Superclasse la moins commune" (oui, pensez au LCM) des deuxième et troisième paramètres. Cela nous donnera l'opérateur ternaire "type". Encore une fois, ce que je viens de dire est une simplification extrême (considérez 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.Datepuisqu'il s'agit de la "Superclasse la moins commune" pour la paire Timestamp/ Time.

Puisqu'il nullpeut être autoboxé à n'importe quoi, la "Superclasse la moins commune" est la Integerclasse et ce sera le type de retour de l'expression conditionnelle (opérateur ternaire) ci-dessus. La valeur de retour sera alors un pointeur nul de type Integeret 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 Integera NullPointerExceptionest lancé. Cela se produit car la machine virtuelle Java tente d'appeler la fonction null.intValue(), où nullest le résultat de la détection automatique.

À mon avis (et comme mon avis n'est pas dans la spécification du langage Java, beaucoup de gens trouveront cela faux de toute façon), le compilateur fait un mauvais travail en évaluant l'expression dans votre question. Étant donné que vous avez écrit, true ? param1 : param2le compilateur doit déterminer tout de suite que le premier paramètre - null- sera renvoyé et il doit générer une erreur de compilation. C'est un peu similaire à lorsque vous écrivez while(true){} etc...et que le compilateur se plaint du code 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, je crois que j'avais tort de dire qu'une nullvaleur peut être encadrée / auto-indexée sur n'importe quoi. En parlant de la classe Integer, la boxe explicite consiste à invoquer le new Integer(...)constructeur ou peut-être le Integer.valueOf(int i);(j'ai trouvé cette version quelque part). Le premier lancerait un NumberFormatException(et cela n'arrive pas) tandis que le second n'aurait tout simplement pas de sens car un intne peut pas être null...

Marsellus Wallace
la source
1
Le nullcode original de l'OP n'est pas encadré. La façon dont cela fonctionne est: le compilateur suppose que le nullest une référence à un entier. En utilisant les règles des types d'expressions ternaires, il décide que l'expression entière est une expression Integer. Il génère ensuite du code pour placer automatiquement le 1(au cas où la condition serait évaluée à false). Au cours de l'exécution, la condition est évaluée comme truetelle pour l'expression null. Lorsque vous essayez de renvoyer un intdepuis la fonction, le nullest déballé. Cela jette alors un NPE. (Le compilateur pourrait optimiser la plupart de cela.)
Ted Hopp
4

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

Avoir
la source
2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing 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
}
Youans
la source
0

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 une boîte automatique.

Je suppose que le compilateur voit le type de retour de l'expression comme Object.

Jon
la source