Perché la modifica della variabile restituita in un blocco finally non modifica il valore restituito?


146

Ho una semplice classe Java come mostrato di seguito:

public class Test {

    private String s;

    public String foo() {
        try {
            s = "dev";
            return s;
        } 
        finally {
            s = "override variable s";
            System.out.println("Entry in finally Block");  
        }
    }

    public static void main(String[] xyz) {
        Test obj = new Test();
        System.out.println(obj.foo());
    }
}

E l'output di questo codice è questo:

Entry in finally Block
dev  

Perché snon viene ignorato nel finallyblocco, ma controlla l'output stampato?


11
dovresti mettere la dichiarazione di ritorno sul blocco finally, ripeti con me, infine il blocco viene sempre eseguito
Juan Antonio Gomez Moriano

1
nel tentativo di bloccare restituisce s
Devendra il

6
L'ordine delle dichiarazioni è importante. Stai tornando sprima di cambiarne il valore.
Peter Lawrey,

6
Ma, si può restituire il nuovo valore nel finallyblocco , a differenza di C # (che non è possibile)
Alvin Wong

Risposte:


167

Il tryblocco si completa con l'esecuzione returndell'istruzione e il valore sal momento dell'esecuzione returndell'istruzione è il valore restituito dal metodo. Il fatto che la finallyclausola in seguito cambi il valore di s(al returntermine dell'istruzione) non modifica (a quel punto) il valore di ritorno.

Si noti che quanto sopra riguarda le modifiche al valore di sse stesso nel finallyblocco, non all'oggetto a cui fa sriferimento. Se sfosse un riferimento a un oggetto mutabile (che Stringnon lo è) e il contenuto dell'oggetto fosse cambiato nel finallyblocco, tali cambiamenti sarebbero visibili nel valore restituito.

Le regole dettagliate su come funziona tutto questo sono disponibili nella Sezione 14.20.2 della specifica del linguaggio Java . Si noti che l'esecuzione di returnun'istruzione conta come una brusca terminazione del tryblocco (si applica la sezione che inizia " Se l'esecuzione del blocco try si completa bruscamente per qualsiasi altro motivo R .... "). Vedere la sezione 14.17 del JLS per il motivo per cui returnun'istruzione è una brusca interruzione di un blocco.

A titolo di ulteriore dettaglio: se sia il tryblocco che il finallyblocco di try-finallyun'istruzione terminano bruscamente a causa di returnistruzioni, si applicano le seguenti regole dal §14.20.2:

Se l'esecuzione del tryblocco viene completata bruscamente per qualsiasi altro motivo R [oltre a generare un'eccezione], il finallyblocco viene eseguito e quindi esiste una scelta:

  • Se il finallyblocco si completa normalmente, l' tryistruzione si completa bruscamente per il motivo R.
  • Se il finallyblocco si completa bruscamente per il motivo S, la tryfrase si completa bruscamente per il motivo S (e il motivo R viene scartato).

Il risultato è che l' returnistruzione nel finallyblocco determina il valore restituito dell'intera try-finallyistruzione e il valore restituito dal tryblocco viene scartato. Una cosa simile si verifica in try-catch-finallyun'istruzione se il tryblocco genera un'eccezione, viene catturato da un catchblocco e sia il catchblocco che il finallyblocco hanno returnistruzioni.


Se uso la classe StringBuilder anziché String piuttosto che aggiungere un valore nel blocco finalmente, cambia il valore restituito. Perché?
Devendra,

6
@dev - Ne discuto nel secondo paragrafo della mia risposta. Nella situazione che descrivi, il finallyblocco non cambia l'oggetto restituito (il StringBuilder) ma può cambiare gli interni dell'oggetto. Il finallyblocco viene eseguito prima che il metodo effettivamente ritorni (anche se l' returnistruzione è terminata), quindi tali modifiche si verificano prima che il codice chiamante veda il valore restituito.
Ted Hopp,

prova con List, ottieni lo stesso comportamento di StringBuilder.
Yogesh Prajapati,

1
@yogeshprajapati - Yep. Lo stesso vale con qualsiasi valore di ritorno mutabile ( StringBuilder, List, Set, fino alla nausea): se si modifica il contenuto del finallyblocco, quindi tali modifiche sono visti nel codice chiamante quando il metodo infine uscite.
Ted Hopp,

Questo dice cosa succede, non dice perché (sarebbe particolarmente utile se indicasse dove questo è stato coperto nel JLS).
TJ Crowder,

65

Perché il valore restituito viene messo nello stack prima della chiamata a finalmente.


3
È vero, ma non risponde alla domanda dell'OP sul perché la stringa restituita non sia cambiata. Ciò ha a che fare con l'immutabilità della stringa e i riferimenti rispetto agli oggetti più che spingere il valore restituito nello stack.
templatetypedef

Entrambi i problemi sono correlati.
Tordek,

2
@templatetypedef anche se String fosse mutabile, l'utilizzo =non lo avrebbe mutato.
Owen,

1
@Saul - Il punto di Owen (che è corretto) era che l'immutabilità non ha nulla a che fare con il motivo per cui il finallyblocco di OP non ha influenzato il valore di ritorno. Penso che ciò a cui potrebbe essere arrivato templatetypedef (anche se questo non è chiaro) è che, poiché il valore restituito è un riferimento a un oggetto immutabile, anche la modifica del codice nel finallyblocco (diverso dall'uso di un'altra returnistruzione) non potrebbe influire sul valore restituito dal metodo
Ted Hopp,

1
@templatetypedef No, non lo è. Niente a che vedere con l'immutabilità delle stringhe. Lo stesso accadrebbe con qualsiasi altro tipo. Ha a che fare con la valutazione dell'espressione return prima di inserire il blocco finally, in altre parole "spingere il valore restituito nello stack".
Marchese di Lorne il

32

Se guardiamo all'interno del bytecode, noteremo che JDK ha apportato un'ottimizzazione significativa e il metodo foo () assomiglia a:

String tmp = null;
try {
    s = "dev"
    tmp = s;
    s = "override variable s";
    return tmp;
} catch (RuntimeException e){
    s = "override variable s";
    throw e;
}

E bytecode:

0:  ldc #7;         //loading String "dev"
2:  putstatic   #8; //storing it to a static variable
5:  getstatic   #8; //loading "dev" from a static variable
8:  astore_0        //storing "dev" to a temp variable
9:  ldc #9;         //loading String "override variable s"
11: putstatic   #8; //setting a static variable
14: aload_0         //loading a temp avariable
15: areturn         //returning it
16: astore_1
17: ldc #9;         //loading String "override variable s"
19: putstatic   #8; //setting a static variable
22: aload_1
23: athrow

java ha preservato la modifica della stringa "dev" prima di tornare. In effetti qui non esiste alcun blocco finalmente.


Questa non è un'ottimizzazione. Questa è semplicemente un'implementazione della semantica richiesta.
Marchese di Lorne il

22

Ci sono 2 cose degne di nota qui:

  • Le stringhe sono immutabili. Quando si imposta s su "ignora variabile s", si imposta s in modo da fare riferimento alla stringa incorporata, senza alterare il buffer di caratteri inerente all'oggetto s per passare a "sovrascrivere variabile s".
  • È stato inserito un riferimento a s nello stack per tornare al codice chiamante. Successivamente (quando viene eseguito il blocco finally), la modifica del riferimento non dovrebbe fare nulla per il valore di ritorno già nello stack.

1
quindi se prendo lo stringbuffer di pungiglione verrà ignorato ??
Devendra,

10
@dev - Se dovessi modificare il contenuto del buffer nella finallyclausola, verrebbe visualizzato nel codice chiamante. Tuttavia, se hai assegnato un nuovo stringbuffer a s, il comportamento sarebbe lo stesso di adesso.
Ted Hopp,

sì, se aggiungo il buffer delle stringhe nel blocco, le modifiche si riflettono sull'output.
Devendra,

@ 0xCAFEBABE hai anche dato un'ottima risposta e concetti, ti ringrazio di cuore.
Devendra,

13

Cambio un po 'il tuo codice per dimostrare il punto di Ted.

Come puoi vedere nell'output sè infatti cambiato ma dopo il ritorno.

public class Test {

public String s;

public String foo() {

    try {
        s = "dev";
        return s;
    } finally {
        s = "override variable s";
        System.out.println("Entry in finally Block");

    }
}

public static void main(String[] xyz) {
    Test obj = new Test();
    System.out.println(obj.foo());
    System.out.println(obj.s);
}
}

Produzione:

Entry in finally Block 
dev 
override variable s

perché la stringa sovrascritta non ritorna.
Devendra,

2
Come Ted e Tordek hanno già detto "il valore restituito viene messo in pila prima che venga finalmente eseguito"
Frank

1
Sebbene queste siano buone informazioni aggiuntive, sono riluttante a votarle, perché non rispondono (da sole) alla domanda.
Joachim Sauer,

5

Tecnicamente parlando, il returnblocco in try non verrà ignorato se finallyviene definito un blocco, solo se quel blocco infine include anche a return.

È una decisione di progettazione dubbia che probabilmente è stata un errore in retrospettiva (molto simile ai riferimenti essendo nullable / mutable per impostazione predefinita e, secondo alcune, controllate le eccezioni). In molti modi questo comportamento è esattamente coerente con la comprensione colloquiale di ciò che finallysignifica - "qualunque cosa accada prima nel tryblocco, esegui sempre questo codice". Quindi, se si ritorna vero da un finallyblocco, l'effetto complessivo deve essere sempre return s, no?

In generale, questo è raramente un buon linguaggio, e dovresti usare i finallyblocchi liberamente per ripulire / chiudere le risorse ma raramente se mai restituire un valore da esse.


È dubbio perché? È congruente con l'ordine di valutazione in tutti gli altri contesti.
Marchese di Lorne il

0

Prova questo: se vuoi stampare il valore di override di s.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}

1
dà un avvertimento .. finalmente il blocco non si completa normalmente
Devendra
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.