Il metodo Java con tipo restituito viene compilato senza istruzione return


228

Domanda 1:

Perché il seguente codice viene compilato senza avere un'istruzione return?

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

Avviso: se aggiungo return dopo un po ', ottengo un Unreachable Code Error.

Domanda 2:

D'altra parte, perché viene compilato il seguente codice,

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

anche se non segue.

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

2
Non un duplicato di stackoverflow.com/questions/16789832/… , grazie alla seconda metà della seconda domanda.
TJ Crowder,

Risposte:


274

Domanda 1:

Perché il seguente codice viene compilato senza avere un'istruzione return?

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

Questo è coperto da JLS§8.4.7 :

Se viene dichiarato che un metodo ha un tipo restituito (§8.4.5), si verifica un errore in fase di compilazione se il corpo del metodo può essere completato normalmente (§14.1).

In altre parole, un metodo con un tipo restituito deve restituire solo usando un'istruzione return che fornisce un valore restituito; al metodo non è consentito "abbandonare la fine del suo corpo". Vedere §14.17 per le regole precise sulle dichiarazioni di ritorno in un corpo del metodo.

È possibile che un metodo abbia un tipo di restituzione e tuttavia non contenga dichiarazioni di restituzione. Ecco un esempio:

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

Dato che il compilatore sa che il ciclo non si chiuderà mai ( trueè sempre vero, ovviamente), sa che la funzione non può "tornare normalmente" (abbandonare la fine del suo corpo), e quindi va bene che non ci sia return.

Domanda 2:

D'altra parte, perché viene compilato il seguente codice,

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

anche se non segue.

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

Nel 0 == 0caso, il compilatore sa che il ciclo non terminerà mai ( 0 == 0sarà sempre vero). Ma non lo sa per b == b.

Perchè no?

Il compilatore comprende espressioni costanti (§15.28) . Citando §15.2 - Forme di espressioni (perché stranamente questa frase non è in §15.28) :

Alcune espressioni hanno un valore che può essere determinato al momento della compilazione. Queste sono espressioni costanti (§15.28).

Nel tuo b == besempio, poiché è coinvolta una variabile, non è un'espressione costante e non è specificata per essere determinata al momento della compilazione. Possiamo vedere che sarà sempre vero in questo caso (anche se se bfosse un double, come ha sottolineato QBrute , potremmo facilmente essere ingannati Double.NaN, che non== è esso stesso ), ma JLS specifica solo che le espressioni costanti sono determinate al momento della compilazione , non consente al compilatore di provare a valutare espressioni non costanti. bayou.io ha sollevato un buon punto perché no: se inizi a cercare di determinare espressioni che coinvolgono variabili al momento della compilazione, dove ti fermi? b == bè ovvio (er, per non-NaNvalori), ma che dire a + b == b + a? Oppure (a + b) * 2 == a * 2 + b * 2? Tracciare la linea alle costanti ha senso.

Quindi dal momento che non "determina" l'espressione, il compilatore non sa che il ciclo non si chiuderà mai, quindi pensa che il metodo possa tornare normalmente - cosa che non gli è permesso fare, perché è necessario usarlo return. Quindi si lamenta della mancanza di a return.


34

Può essere interessante pensare a un tipo restituito dal metodo non come una promessa di restituire un valore del tipo specificato, ma come una promessa di non restituire un valore che non è del tipo specificato. Pertanto, se non restituisci mai nulla, non stai infrangendo la promessa e quindi una delle seguenti condizioni è legale:

  1. Loop per sempre:

    X foo() {
        for (;;);
    }
  2. Ricorrere per sempre:

    X foo() {
        return foo();
    }
  3. Eliminare un'eccezione:

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

(Trovo che la ricorsione sia divertente a cui pensare: il compilatore ritiene che il metodo restituirà un valore di tipo X(qualunque cosa sia), ma non è vero, perché non è presente alcun codice che abbia idea di come creare o procurare un X.)


8

Guardando il codice byte, se ciò che viene restituito non corrisponde alla definizione, riceverai un errore di compilazione.

Esempio:

for(;;) mostrerà i bytecode:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Si noti la mancanza di alcun codice di ritorno

Questo non ha mai un ritorno, e quindi non restituisce il tipo sbagliato.

Per confronto, un metodo come:

public String getBar() { 
    return bar; 
}

Restituirà i seguenti bytecode:

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

Nota "areturn" che significa "restituisci un riferimento"

Ora se facciamo quanto segue:

public String getBar() { 
    return 1; 
}

Restituirà i seguenti bytecode:

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

Ora possiamo vedere che il tipo nella definizione non corrisponde al tipo di ritorno di ireturn, il che significa return int.

Quindi, in realtà, ciò che si riduce è che se il metodo ha un percorso di ritorno, quel percorso deve corrispondere al tipo di ritorno. Ma ci sono casi nel bytecode in cui non viene generato alcun percorso di ritorno e quindi nessuna violazione della regola.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.