Die Rückgabe von null als int ist mit dem ternären Operator zulässig, jedoch nicht mit der if-Anweisung

186

Schauen wir uns den einfachen Java-Code im folgenden Snippet an:

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

In diesem einfachsten Java-Code gibt die temp()Methode keinen Compilerfehler aus, obwohl der Rückgabetyp der Funktion lautet int, und wir versuchen, den Wert null(über die Anweisung return true ? null : 0;) zurückzugeben. Beim Kompilieren verursacht dies offensichtlich die Laufzeitausnahme NullPointerException.

Allerdings scheint es , dass die gleiche Sache falsch ist , wenn wir den ternären Operator mit einer darstellen ifAussage (wie im same()Verfahren), das tut einen Fehler bei der Kompilierung Ausgabe! Warum?

Löwe
quelle
6
Auch int foo = (true ? null : 0)und new Integer(null)beide kompilieren gut, wobei die zweite die explizite Form des Autoboxing ist.
Izkata
2
@Izkata das Problem hier ist für mich zu verstehen, warum der Compiler versucht, Autobox nullzu Integer... Das würde so aussehen, als würde ich "raten" oder "Dinge zum Laufen bringen " ...
Marsellus Wallace
1
... Huhm, ich dachte, ich hätte dort eine Antwort, da der Integer-Konstruktor (was die Dokumente, die ich gefunden habe, für Autoboxing verwenden) einen String als Argument verwenden dürfen (der null sein kann). Sie sagen jedoch auch, dass der Konstruktor identisch mit der Methode parseInt () handelt, die eine NumberFormatException auslösen würde, wenn eine Null übergeben wird ...
Izkata
3
@Izkata - Das String-Argument c'tor für Integer ist keine Autoboxing-Oepration. Ein String kann nicht automatisch an eine Ganzzahl angehängt werden. (Die Funktion Integer foo() { return "1"; }wird nicht kompiliert.)
Ted Hopp
5
Cool, habe etwas Neues über den ternären Operator gelernt!
Oksayt

Antworten:

118

Der Compiler interpretiert nullals Nullreferenz auf a Integer, wendet die Autoboxing- / Unboxing-Regeln für den bedingten Operator an (wie in der Java-Sprachspezifikation, 15.25 beschrieben ) und fährt glücklich fort. Dadurch wird NullPointerExceptionzur Laufzeit eine generiert , die Sie durch Ausprobieren bestätigen können.

Ted Hopp
quelle
Welcher Punkt wird Ihrer Meinung nach angesichts des von Ihnen veröffentlichten Links zur Java-Sprachspezifikation bei der obigen Frage ausgeführt? Der letzte (da ich immer noch versuche zu verstehen capture conversionund lub(T1,T2)) ?? Ist es wirklich möglich, Boxen auf einen Nullwert anzuwenden? Wäre das nicht wie "Raten"?
Marsellus Wallace
´ @ Gevorg Ein Nullzeiger ist ein gültiger Zeiger auf jedes mögliche Objekt, sodass dort nichts Schlimmes passieren kann. Der Compiler geht nur davon aus, dass null eine Ganzzahl ist, die er dann automatisch in int kopieren kann.
Voo
1
@Gevorg - Siehe nowaqs Kommentar und meine Antwort auf seinen Beitrag. Ich denke, er hat die richtige Klausel gewählt. lub(T1,T2)ist der spezifischste Referenztyp, der in der Typhierarchie von T1 und T2 gemeinsam ist. (Beide teilen sich mindestens Object, daher gibt es immer einen bestimmten Referenztyp.)
Ted Hopp
8
@Gevorg - nullnicht boxed in eine Integer, wird interpretiert als Hinweis auf einen Integer (einen NULL - Verweis, aber das ist kein Problem). Aus der Null wird kein Integer-Objekt erstellt, daher gibt es keinen Grund für eine NumberFormatException.
Ted Hopp
1
@Gevorg - Wenn Sie sich die Regeln für die Boxkonvertierung ansehen und sie anwenden null(was kein primitiver numerischer Typ ist), lautet die anwendbare Klausel "Wenn p ein Wert eines anderen Typs ist, entspricht die Boxkonvertierung einer Identitätskonvertierung ". Also Boxing-Konvertierung nullin IntegerAusbeuten null, ohne einen IntegerKonstruktor aufzurufen .
Ted Hopp
40

Ich denke, der Java-Compiler interpretiert true ? null : 0als IntegerAusdruck, der implizit konvertiert werden kann intund möglicherweise gibt NullPointerException.

Für den zweiten Fall ist der Ausdruck nullvom speziellen Nulltyp ( siehe) , sodass der Code return nullden Typ nicht übereinstimmt.

Vlad
quelle
2
Ich nehme an, das hängt mit Auto-Boxen zusammen? Vermutlich würde die erste Rückgabe nicht vor Java 5 kompiliert werden, oder?
Michael McGowan
@Michael Dies scheint der Fall zu sein, wenn Sie die Konformitätsstufe von Eclipse auf Pre-5 setzen.
Jonathon Faust
@Michael: Das sieht definitiv nach Auto-Boxing aus (ich bin ziemlich neu in Java und kann keine genauere Aussage machen - sorry).
Vlad
1
@Vlad wie würde der Compiler am Ende interpretieren true ? null : 0als Integer? Durch Autoboxing 0zuerst?
Marsellus Wallace
1
@Gevorg: Schauen Sie hier : Ansonsten sind der zweite und dritte Operand vom Typ S1 bzw. S2. Sei T1 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S1 ergibt, und sei T2 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S2 ergibt. und den folgenden Text.
Vlad
32

Eigentlich ist alles in der Java-Sprachspezifikation erklärt .

Der Typ eines bedingten Ausdrucks wird wie folgt bestimmt:

  • Wenn der zweite und der dritte Operand denselben Typ haben (der der Nulltyp sein kann), ist dies der Typ des bedingten Ausdrucks.

Daher (true ? null : 0)erhält die "Null" in Ihrem einen int-Typ und wird dann automatisch in eine Ganzzahl geschrieben.

Versuchen Sie etwas Ähnliches, um dies zu überprüfen, (true ? null : null)und Sie erhalten den Compilerfehler.

nowaq
quelle
3
Diese Regelklausel gilt jedoch nicht: Der zweite und der dritte Operand haben nicht denselben Typ.
Ted Hopp
1
Dann scheint die Antwort in der folgenden Aussage zu sein:> Andernfalls sind der zweite und der dritte Operand vom Typ S1 bzw. S2. Sei T1 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S1 ergibt, und sei T2 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S2 ergibt. Der Typ des bedingten Ausdrucks ergibt sich aus der Anwendung der Capture-Konvertierung (§5.1.10) auf lub (T1, T2) (§15.12.2.7).
Nowaq
Ich denke, das ist die anwendbare Klausel. Anschließend wird versucht, das automatische Entpacken anzuwenden, um einen intWert von der Funktion zurückzugeben, der eine NPE verursacht.
Ted Hopp
@nowaq Ich dachte das auch. Wenn Sie jedoch versuchen, explizit Feld nullzu Integermit new Integer(null);„Let T1 sein , die Art , dass die Ergebnisse aus der Anwendung Boxen Umstellung auf S1 ...“ Sie bekommen würden , NumberFormatExceptionund dies ist nicht der Fall ...
Marsellus Wallace
@Gevorg Ich würde denken, da beim Boxen eine Ausnahme auftritt, erhalten wir hier KEIN Ergebnis. Der Compiler ist lediglich verpflichtet, Code zu generieren, der der Definition folgt, die er ausführt. Wir erhalten nur die Ausnahme, bevor wir fertig sind.
Voo
25

Im Fall der ifAnweisung wird die nullReferenz nicht als IntegerReferenz behandelt, da sie nicht an einem Ausdruck teilnimmt , der die Interpretation als solche erzwingt. Daher kann der Fehler leicht zur Compile-Zeit gefangen werden , weil es klarer ist ein Typ Fehler.

Was den bedingten Operator betrifft, so ? :beantwortet die Java-Sprachspezifikation §15.25 „Bedingter Operator “ dies in den Regeln für die Anwendung der Typkonvertierung:

  • Wenn der zweite und der dritte Operand denselben Typ haben (der der Nulltyp sein kann), ist dies der Typ des bedingten Ausdrucks.

    Gilt nicht, weil nullnicht int.

  • Wenn einer der zweiten und dritten Operanden vom Typ Boolean und der Typ des anderen vom Typ Boolean ist, ist der Typ des bedingten Ausdrucks boolean.

    Gilt nicht , weil weder , nullnoch intist booleanoder Boolean.

  • Wenn einer der zweiten und dritten Operanden vom Typ Null ist und der Typ des anderen ein Referenztyp ist, ist der Typ des bedingten Ausdrucks dieser Referenztyp.

    Gilt nicht, da nulles vom Nulltyp ist, aber intkein Referenztyp.

  • Andernfalls gibt es mehrere Fälle, wenn der zweite und dritte Operand Typen haben, die in numerische Typen konvertierbar sind (§5.1.8): […]

    Gilt: nullwird als in einen numerischen Typ konvertierbar behandelt und in §5.1 definiert. 8 "Unboxing Conversion", um a zu werfen NullPointerException.
Jon Purdy
quelle
Wenn 0ein Autobox aktiviert ist, führt Integerder Compiler den letzten Fall der "ternären Operatorregeln" aus, wie in der Java-Sprachspezifikation beschrieben. Wenn das stimmt, fällt es mir schwer zu glauben, dass es dann zu Fall 3 derselben Regeln springen würde, die eine Null und einen Referenztyp haben, bei denen der Rückgabewert des ternären Operators der Referenztyp (Integer) ist. .
Marsellus Wallace
@Gevorg - Warum ist es schwer zu glauben, dass der ternäre Operator einen zurückgibt Integer? Genau das passiert; Die NPE wird generiert, indem versucht wird, den Ausdruckswert zu entpacken, um einen intvon der Funktion zurückzugeben. Ändern Sie die Funktion, um eine zurückzugeben, Integerund sie wird nullohne Probleme zurückgegeben.
Ted Hopp
2
@ TedHopp: Gevorg hat auf eine frühere Überarbeitung meiner Antwort geantwortet, die falsch war. Sie sollten die Diskrepanz ignorieren.
Jon Purdy
@JonPurdy "Ein Typ soll in einen numerischen Typ konvertierbar sein, wenn es sich um einen numerischen Typ handelt, oder es ist ein Referenztyp, der durch Unboxing-Konvertierung in einen numerischen Typ konvertiert werden kann", und ich glaube nicht, dass dies nullin diese Kategorie fällt . Außerdem würden wir dann in den Schritt "Andernfalls wird die binäre numerische Heraufstufung (§5.6.2) angewendet ... Beachten Sie, dass die binäre numerische Heraufstufung eine Unboxing-Konvertierung (§5.1.8) ..." ausführt, um den Rückgabetyp zu bestimmen. Die Unboxing-Konvertierung würde jedoch eine NPE generieren. Dies geschieht nur zur Laufzeit und nicht beim Versuch, den ternären Operatortyp zu bestimmen. Ich bin immer noch verwirrt.
Marsellus Wallace
@Gevorg: Unboxing findet zur Laufzeit statt. Das nullwird so behandelt, als hätte es Typ int, ist aber eigentlich gleichbedeutend mit throw new NullPointerException(), das ist alles.
Jon Purdy
11

Das erste, was zu beachten ist, ist, dass ternäre Java-Operatoren einen "Typ" haben und dass dies vom Compiler bestimmt und berücksichtigt wird, unabhängig davon, welche tatsächlichen / realen Typen der zweite oder dritte Parameter sind. Abhängig von mehreren Faktoren wird der ternäre Operatortyp auf unterschiedliche Weise bestimmt, wie in der Java-Sprachspezifikation 15.26 dargestellt

In der obigen Frage sollten wir den letzten Fall betrachten:

Ansonsten sind der zweite und der dritte Operand vom Typ S1 bzw. S2 . Sei T1 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S1 ergibt , und sei T2 der Typ, der sich aus der Anwendung der Boxkonvertierung auf S2 ergibt . Der Typ des bedingten Ausdrucks ergibt sich aus der Anwendung der Capture-Konvertierung (§5.1.10) auf lub (T1, T2) (§15.12.2.7).

Dies ist bei weitem der komplexeste Fall, wenn Sie sich die Anwendung der Capture-Konvertierung (§5.1.10) und vor allem lub (T1, T2) ansehen .

Im Klartext und nach einer extremen Vereinfachung können wir den Prozess als Berechnung der "Least Common Superclass" (ja, denken Sie an das LCM) des zweiten und dritten Parameters beschreiben. Dies gibt uns den ternären Operator "Typ". Was ich gerade gesagt habe, ist eine extreme Vereinfachung (betrachten Sie Klassen, die mehrere gemeinsame Schnittstellen implementieren).

Wenn Sie beispielsweise Folgendes versuchen:

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

Sie werden feststellen, dass der resultierende Typ des bedingten Ausdrucks java.util.Datedie "Least Common Superclass" für das Timestamp/ Timepair ist.

Da nullfür alles eine Autobox erstellt werden kann, ist die "Least Common Superclass" die IntegerKlasse und dies ist der Rückgabetyp des obigen bedingten Ausdrucks (ternärer Operator). Der Rückgabewert ist dann ein Nullzeiger vom Typ, Integerund dieser wird vom ternären Operator zurückgegeben.

Zur Laufzeit, wenn die Java Virtual Machine entpackt, wird das Integera NullPointerExceptionausgelöst. Dies geschieht, weil die JVM versucht, die Funktion aufzurufen null.intValue(), nullwas das Ergebnis einer Autobox ist.

Meiner Meinung nach (und da meine Meinung nicht in der Java-Sprachspezifikation enthalten ist, werden viele Leute es sowieso falsch finden) leistet der Compiler schlechte Arbeit bei der Bewertung des Ausdrucks in Ihrer Frage. Vorausgesetzt, Sie haben geschrieben, sollte true ? param1 : param2der Compiler sofort bestimmen, dass der erste Parameter - null- zurückgegeben wird, und er sollte einen Compilerfehler erzeugen. Dies ist etwas ähnlich wie beim Schreiben while(true){} etc...und der Compiler beschwert sich über den Code unter der Schleife und kennzeichnet ihn mit Unreachable Statements.

Dein zweiter Fall ist ziemlich einfach und diese Antwort ist schon zu lang ...;)

KORREKTUR:

Nach einer weiteren Analyse glaube ich, dass ich zu Unrecht gesagt habe, dass ein nullWert für alles eingerahmt / autoboxed werden kann. Wenn wir über die Klasse Integer sprechen, besteht explizites Boxen darin, den new Integer(...)Konstruktor oder vielleicht das aufzurufen Integer.valueOf(int i);(ich habe diese Version irgendwo gefunden). Ersteres würde ein werfen NumberFormatException(und das passiert nicht), während das zweite einfach keinen Sinn ergeben würde, da ein intnicht sein kann null...

Marsellus Wallace
quelle
1
Der nullOriginalcode des OP ist nicht verpackt. So funktioniert es: Der Compiler geht davon aus, dass nulles sich um eine Referenz auf eine Ganzzahl handelt. Unter Verwendung der Regeln für ternäre Ausdruckstypen wird entschieden, dass der gesamte Ausdruck ein ganzzahliger Ausdruck ist. Es generiert dann Code für die Autobox 1(falls die Bedingung ausgewertet wird false). Während der Ausführung wird die Bedingung als ausgewertet, truesodass der Ausdruck als ausgewertet wird null. Wenn Sie versuchen, ein intvon der Funktion zurückzugeben, nullwird das nicht ausgepackt. Das wirft dann eine NPE. (Der Compiler könnte das meiste davon weg optimieren.)
Ted Hopp
4

Tatsächlich kann der Ausdruck im ersten Fall ausgewertet werden, da der Compiler weiß, dass er als ausgewertet werden muss. IntegerIm zweiten Fall kann der Typ des Rückgabewerts ( null) jedoch nicht bestimmt werden, sodass er nicht kompiliert werden kann. Wenn Sie es Integerumwandeln, wird der Code kompiliert.

Bekommen
quelle
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
quelle
0

Wie wäre es damit:

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

    }

}

Die Ausgabe ist wahr, wahr.

Die Eclipse-Farbe codiert die 1 im bedingten Ausdruck als Autobox.

Ich vermute, der Compiler sieht den Rückgabetyp des Ausdrucks als Objekt.

Jon
quelle