Java-Methode mit Rückgabetyp wird ohne return-Anweisung kompiliert

228

Frage 1:

Warum wird der folgende Code ohne return-Anweisung kompiliert?

public int a() {
    while(true);
}

Hinweis: Wenn ich nach einiger Zeit eine Rückgabe hinzufüge, erhalte ich eine Unreachable Code Error.

Frage 2:

Warum wird der folgende Code kompiliert?

public int a() {
    while(0 == 0);
}

obwohl das folgende nicht.

public int a(int b) {
    while(b == b);
}
Willi Mentzel
quelle
2
Dank der zweiten Hälfte der zweiten Frage kein Duplikat von stackoverflow.com/questions/16789832/… .
TJ Crowder

Antworten:

274

Frage 1:

Warum wird der folgende Code ohne return-Anweisung kompiliert?

public int a() 
{
    while(true);
}

Dies wird durch JLS§8.4.7 abgedeckt :

Wenn für eine Methode ein Rückgabetyp deklariert wird (§8.4.5), tritt ein Fehler bei der Kompilierung auf, wenn der Hauptteil der Methode normal abgeschlossen werden kann (§14.1).

Mit anderen Worten, eine Methode mit einem Rückgabetyp darf nur mithilfe einer return-Anweisung zurückgegeben werden, die eine Wertrückgabe bereitstellt. Die Methode darf nicht "das Ende ihres Körpers fallen lassen". In §14.17 finden Sie die genauen Regeln für return-Anweisungen in einem Methodenkörper.

Es ist möglich, dass eine Methode einen Rückgabetyp hat und dennoch keine Rückgabeanweisungen enthält. Hier ist ein Beispiel:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

Da der Compiler weiß, dass die Schleife niemals beendet wird ( trueist natürlich immer wahr), weiß er, dass die Funktion nicht "normal zurückkehren" kann (das Ende ihres Körpers fallen lassen), und daher ist es in Ordnung, dass es keine gibt return.

Frage 2:

Warum wird der folgende Code kompiliert?

public int a() 
{
    while(0 == 0);
}

obwohl das folgende nicht.

public int a(int b)
{
    while(b == b);
}

In diesem 0 == 0Fall weiß der Compiler, dass die Schleife niemals beendet wird (das 0 == 0wird immer wahr sein). Aber das weiß es nicht für b == b.

Warum nicht?

Der Compiler versteht konstante Ausdrücke (§15.28) . Zitieren von §15.2 - Formen von Ausdrücken (weil dieser Satz seltsamerweise nicht in §15.28 enthalten ist) :

Einige Ausdrücke haben einen Wert, der zur Kompilierungszeit festgelegt werden kann. Dies sind konstante Ausdrücke (§15.28).

b == bDa es sich in Ihrem Beispiel um eine Variable handelt, handelt es sich nicht um einen konstanten Ausdruck und es wird nicht angegeben, dass er zur Kompilierungszeit bestimmt werden soll. Wir können sehen, dass es in diesem Fall immer wahr sein wird (obwohl, wenn bein double, wie QBrute betonte , leicht getäuscht werden könnte Double.NaN, was nicht ==selbst ist ), aber das JLS spezifiziert nur, dass konstante Ausdrücke zur Kompilierungszeit bestimmt werden Der Compiler kann nicht versuchen, nicht konstante Ausdrücke auszuwerten. bayou.io hat einen guten Punkt angesprochen , warum nicht: Wenn Sie versuchen, Ausdrücke mit Variablen zur Kompilierungszeit zu bestimmen, wo hören Sie dann auf? b == bist offensichtlich (ähm, für Nicht-NaNWerte), aber was ist mit a + b == b + a? Oder (a + b) * 2 == a * 2 + b * 2? Das Zeichnen der Linie bei Konstanten ist sinnvoll.

Da der Ausdruck nicht "bestimmt" wird, weiß der Compiler nicht, dass die Schleife niemals beendet wird, und glaubt daher, dass die Methode normal zurückkehren kann - was nicht zulässig ist, da sie verwendet werden muss return. Es beschwert sich also über das Fehlen eines return.

TJ Crowder
quelle
34

Es kann interessant sein, sich einen Methodenrückgabetyp nicht als Versprechen vorzustellen, einen Wert des angegebenen Typs zurückzugeben, sondern als Versprechen, keinen Wert zurückzugeben, der nicht vom angegebenen Typ ist. Wenn Sie also niemals etwas zurückgeben, brechen Sie das Versprechen nicht, und daher ist Folgendes legal:

  1. Für immer schleifen:

    X foo() {
        for (;;);
    }
  2. Für immer wiederkehren:

    X foo() {
        return foo();
    }
  3. Eine Ausnahme ausschließen:

    X foo() {
        throw new Error();
    }

(Ich finde, dass die Rekursion Spaß macht: Der Compiler glaubt, dass die Methode einen Wert vom Typ X(was auch immer das ist) zurückgibt, aber es ist nicht wahr, da kein Code vorhanden ist, der eine Idee zum Erstellen oder hat beschaffen an X.)

Boann
quelle
8

Wenn der zurückgegebene Bytecode nicht mit der Definition übereinstimmt, wird ein Kompilierungsfehler angezeigt.

Beispiel:

for(;;) zeigt die Bytecodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Beachten Sie das Fehlen eines Rückgabebytecodes

Dies führt nie zu einer Rückgabe und gibt daher nicht den falschen Typ zurück.

Zum Vergleich eine Methode wie:

public String getBar() { 
    return bar; 
}

Gibt die folgenden Bytecodes zurück:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Beachten Sie die "Rückkehr", was "Rückgabe einer Referenz" bedeutet.

Wenn wir nun Folgendes tun:

public String getBar() { 
    return 1; 
}

Gibt die folgenden Bytecodes zurück:

public String getBar();
  Code:
   0:   iconst_1
   1:   ireturn

Jetzt können wir sehen, dass der Typ in der Definition nicht mit dem Rückgabetyp von ireturn übereinstimmt, was return int bedeutet.

Es kommt also wirklich darauf an, dass, wenn die Methode einen Rückgabepfad hat, dieser Pfad mit dem Rückgabetyp übereinstimmen muss. Es gibt jedoch Fälle im Bytecode, in denen überhaupt kein Rückgabeweg generiert wird und somit kein Verstoß gegen die Regel vorliegt.

Philip Devine
quelle