W3docs

Blocco finally in Java

Esegui codice di pulizia in Java con i blocchi finally che vengono sempre eseguiti, che sia stata lanciata un'eccezione o meno.

Un blocco finally viene eseguito indipendentemente da come esce il try — completamento normale, eccezione gestita, eccezione non gestita, o anche un return anticipato. Questa garanzia è il suo unico scopo: è qui che si inserisce il codice di pulizia che deve necessariamente essere eseguito, sempre. Chiudere un file handle, rilasciare un lock, ripristinare lo stato di un thread — qualsiasi cosa la cui assenza lascerebbe il programma in una condizione peggiore di quella iniziale.

La struttura

try {
  // risky code
} catch (SomeException e) {
  // optional — zero or more catches
} finally {
  // always runs after the try (and any matching catch)
}

È possibile abbinare finally a catch, a nessun catch (try { ... } finally { ... }), o a più catch. I blocchi si compongono liberamente.

Cosa significa "viene sempre eseguito"

Un blocco finally viene eseguito quando il controllo lascia il try, indipendentemente da come:

  • Fine normale del blocco tryfinally viene eseguito dopo l'ultima istruzione.
  • Un'eccezione lanciata da tryfinally viene eseguito dopo che il catch corrispondente termina (oppure, se nessun catch corrisponde, appena prima che l'eccezione si propaghi).
  • return all'interno di try o catchfinally viene eseguito prima che il return abbia effetto.
  • break o continue che uscirebbe dal tryfinally viene eseguito prima del salto.

Gli unici modi per saltare finally sono: la JVM stessa termina (System.exit, un'interruzione di corrente, Runtime.halt), un ciclo infinito o un deadlock all'interno del try, oppure Thread.stop (deprecato proprio per questo motivo). Per tutto ciò che si scrive nel normale codice applicativo, finally è una garanzia assoluta.

try {
  return computeAnswer();    // even though there's a return here,
} finally {
  cleanup();                 // this runs before the method actually returns
}

A cosa serve finally

La risposta onesta è: pulizia delle risorse, quasi sempre. Prima che Java 7 introducesse try-with-resources, la struttura canonica era:

InputStream in = null;
try {
  in = new FileInputStream(path);
  // read from in...
} catch (IOException e) {
  // handle
} finally {
  if (in != null) {
    try { in.close(); } catch (IOException ignored) { /* */ }
  }
}

Quel try/catch annidato attorno a close() nel finally è esattamente il tipo di rumore che try-with-resources è stato progettato per eliminare. Lo vedremo nel prossimo capitolo. Ma capire a cosa serve finally rende più sensato il costrutto più recente.

Oltre alle risorse, finally è utile per:

  • Ripristinare lo stato condiviso modificato per la durata del try — incrementare un contatore di profondità, cambiare un flag, scambiare un ThreadLocal.
  • Rilasciare lock acquisiti manualmente (Lock.lock()try { ... } finally { lock.unlock(); }).
  • Fermare timer o chiudere transazioni che non implementano AutoCloseable.

A cosa finally non serve

Non scrivere logica che produce risultati in finally. Il blocco viene eseguito indipendentemente dall'esito — non sa se il try è riuscito. Se si mette commit() in finally, si farà il commit anche in caso di errore.

E non usare return da finally. Questo è uno degli angoli genuinamente pericolosi del linguaggio:

try {
  return 1;
} finally {
  return 2;     // wins — the method returns 2 and the original return is lost
}

Il return (o throw) all'interno di finally sovrascrive qualsiasi return o eccezione proveniente dal try. L'eccezione che stava per propagarsi viene scartata silenziosamente. La maggior parte dei linter segnala questo come un errore per questo motivo. La regola: finally esegue la pulizia; finally non produce valori.

Ordine di esecuzione

Quando sono presenti sia un catch che un finally:

try   { ... }
catch { ... }
finally { ... }

L'ordine è esattamente quello che ci si aspetterebbe: try viene eseguito, se lancia un'eccezione e un catch corrisponde allora viene eseguito quel catch, e finally viene eseguito dopo uno dei due. Se finally lancia a sua volta un'eccezione, quel nuovo lancio sostituisce qualsiasi cosa stesse propagandosi dal try o dal catch — un altro motivo per mantenere finally silenzioso.

Un esempio pratico

Strumentiamo una piccola "transazione" con try/catch/finally e la chiamiamo in tre modi diversi: successo normale, un errore recuperabile e uno non recuperabile. Il finally viene eseguito in tutti e tre i casi, che è l'intero punto.

java— editable, runs on the server

Nella terza chiamata, doWork lancia una RuntimeException che il catch locale non intercetta. Il finally viene comunque eseguito e stampa "release lock" prima che l'eccezione continui a risalire fino a main. Questa è la proprietà che si desidera dal codice di pulizia — non dipende dal fatto che la gestione sia riuscita.

Cosa segue

La struttura "apri una risorsa, lavora con essa, chiudila in finally" è così comune che Java ha costruito una istruzione dedicata per essa. Continua con Java try-with-resources.

Pratica

Pratica
Cosa restituisce questo metodo?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Cosa restituisce questo metodo?\n\n```java\nstatic int demo() {\n try {\n return 1;\n } finally {\n return 2;\n }\n}\n```
Was this page helpful?