2015-06-19 10 views
31

Con il seguente codice:Perché try..finally block non registra l'eccezione originale come soppressa?

try { 
    throw new RuntimeException ("main"); 
} 
finally { 
    throw new RuntimeException ("finally"); 
} 

ottengo questo risultato:

Exception in thread "main" java.lang.RuntimeException: finally 
     at test.main(test.java:12) 

Tuttavia, con l'aggiunta di eccezioni soppressi in Java 7, non sarebbe logico per la lingua per la registrazione originale eccezione "principale" come soppressa quando il blocco finally non riesce con eccezione? Attualmente ho emulare manualmente questo:

try { 
    throw new RuntimeException ("main"); 
} 
catch (RuntimeException exception) { 
    try { 
     throw new RuntimeException ("finally"); 
    } 
    catch (RuntimeException exception2) { 
     exception2.addSuppressed (exception); 
     throw exception2; 
    } 
} 

a ricevere più utile (per capire cosa sta succedendo) risultato:

Exception in thread "main" java.lang.RuntimeException: finally 
     at test.main(test.java:13) 
     Suppressed: java.lang.RuntimeException: main 
       at test.main(test.java:9) 

EDIT: per chiarire ciò che mi chiedo. La versione corrente di Java è 8, le eccezioni soppresse non sono una novità. Ma try..finally ancora non li incorpora. C'è qualcosa che impedisce che ciò accada?

+1

Non dovrebbe essere l'eccezione soppressa quella lanciata in 'finally'? – StenSoft

+1

@StenSoft: anche se probabilmente più logico, ciò interromperebbe completamente la compatibilità con le versioni precedenti. – doublep

+4

Non capisco gli stretti voti, ad essere onesti. Questa è una domanda perfettamente legittima. – fge

risposta

7

Perché try-with-resources è zucchero sintattico e il compilatore Java non espande i normali blocchi try-finally nello stesso modo.

Date un'occhiata al codice seguente:

try(FileInputStream fstream = new FileInputStream("test")) { 
    fstream.read(); 
} 

Quando compilati, e quindi decompilato (utilizzando IntelliJ IDEA) sembra che questo:

FileInputStream fstream = new FileInputStream("test"); 
Throwable var2 = null; 

try { 
    fstream.read(); 
} catch (Throwable var19) { 
    var2 = var19; 
    throw var19; 
} finally { 
    if(fstream != null) { 
     if(var2 != null) { 
      try { 
       fstream.close(); 
      } catch (Throwable var17) { 
       var2.addSuppressed(var17); 
      } 
     } else { 
      fstream.close(); 
     } 
    } 
} 

considerando che tale codice:

FileInputStream fstream = new FileInputStream("test"); 
try { 
    fstream.read(); 
} finally { 
    fstream.close(); 
} 

Sembra esattamente lo stesso quando compilato e decompilato.

Ora, il caso potrebbe sicuramente essere che tutti i blocchi finally devono essere espansi nello stesso modo come è fatto sopra, ma per qualche motivo che è stato trascurato o deciso contro.

Ti suggerisco open a feature request per questo perché penso che sia una funzionalità ragionevole.

+4

Ho letto la domanda come "* Perché il compilatore Java non espande i normali blocchi try-finally nello stesso modo *", anche se penso che questa risposta aggiunga ancora qualcosa di utile. – Marvin

+0

Bene, la domanda è * "C'è qualcosa che impedisce che ciò accada?" * E la ragione per cui non sta accadendo è perché non è implementata. Per quanto riguarda * perché * non è implementato, possiamo solo indovinare o chiedere a Oracle (possibilmente aprendo una richiesta di funzionalità). – Raniz

+0

Penso che nella vita reale, in genere, si verifichi un'eccezione a causa dell'eccezione delle risorse di chiusura. Questo è il motivo per cui, credo, un'interfaccia AutoCloseable deve essere usato per exemple: classe Cleanup implementa AutoCloseable { @Override public void close() throws Exception { cleanup(); }} ... prova (Cleanup pulizia = new Cleanup()) {// qualche eccezione viene generata qui } – aurya

5

Questa non è una risposta autorevole, ma sembra che un tale cambiamento interromperebbe la compatibilità, oppure try-with-resources e try-finally sarebbero incoerenti tra loro.

La semantica in try-with-resources è che l'eccezione generata dal blocco try viene propagata, con l'eccezione generata in finally registrata come soppressa. Questo ha senso da un punto di vista pratico, vuoi catturare la tua "vera" eccezione e poi magari anche gestire la risorsa che non si chiude se lo desideri.

Ma in try-finally è l'eccezione gettato in finally che si propaga (mentre quello da try viene "inghiottita") e quindi abbiamo una scelta di tre soluzioni cattive:

  1. invertire la logica di try-finally per allinearlo con try-with-resources e rovinare il codice (o meglio, bug) compatibilità con tutto il codice precedente.
  2. Continuare a propagare l'eccezione finally con l'eccezione try registrata come soppressa, rendendo i due costrutti incoerenti l'uno con l'altro.
  3. Lascia tutto come è.

Soggettivamente vedo 3 meno grave di 1 o 2, anche se è abbastanza facile argomentare diversamente. Sospetto tuttavia che questo sia stato un dilemma con cui gli sviluppatori linguistici si sono trovati di fronte e hanno scelto l'opzione 3.

+0

Sì, direi che 2 è migliore di 3 (1 è ovviamente fuori domanda). Il motivo è che dal mio punto di vista le eccezioni sono nel 90% dei casi per problemi di debug, solo il 10% è per la cattura. Avere informazioni di debug più preziose, anche se non coerenti con altri casi, è, a mio parere, molto meglio che avere di meno. – doublep

+0

@doublep Questa parte è soggettiva, sono d'accordo e potrebbero esserci argomenti molto validi per tutti e tre. Certamente non volevo dire che 3 è sicuramente l'opzione corretta e tutto il resto è sbagliato. Volevo solo sottolineare un'importante differenza tra i due costrutti, che probabilmente ha influito sulla decisione. – biziclop

+2

... per esempio puoi dire che 1 è l'opzione migliore perché 'try-finally' è concettualmente rotto e risolvendolo (e portandolo in linea con' try-with-resources' è più importante che mantenere la retrocompatibilità per il un po 'di codice che è probabilmente buggy comunque. – biziclop