getta Eccezione in finalmente blocchi


100

Esiste un modo elegante per gestire le eccezioni lanciate in finallyblocco?

Per esempio:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

Come eviti la try/ catchnel finallyblocco?

Risposte:


72

Di solito lo faccio in questo modo:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

Altrove:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
Sì, uso un linguaggio molto simile. Ma non creo una funzione per questo.
OscarRyz

9
Una funzione è utile se è necessario utilizzare l'idioma in alcuni punti della stessa classe.
Darron,

Il controllo per null è ridondante. Se la risorsa era nulla, allora il metodo chiamante è rotto dovrebbe essere corretto. Inoltre, se la risorsa è nulla, probabilmente dovrebbe essere registrata. In caso contrario, una potenziale eccezione viene ignorata silenziosamente.
Dave Jarvis

14
Il controllo per null non è sempre ridondante. Pensa a "resource = new FileInputStream (" file.txt ")" come prima riga del tentativo. Inoltre, questa domanda non riguardava la programmazione orientata agli aspetti che molte persone non usano. Tuttavia, il concetto che l'eccezione non dovrebbe essere semplicemente ignorata è stato gestito in modo più compatto mostrando un'istruzione di log.
Darron

1
Resource=> Closeable?
Dmitry Ginzburg

25

In genere utilizzo uno dei closeQuietlymetodi in org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
Puoi rendere questo metodo più generale con Closeable public static void closeQuietly (Closeable closeable) {
Peter Lawrey,

6
Sì, chiudibile è bello. È un peccato che molte cose (come le risorse JDBC) non lo implementino.
Darron,

22

Se stai usando Java 7 e resourceimplementa AutoClosable, puoi farlo (usando InputStream come esempio):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

Probabilmente un po 'esagerato, ma forse utile se lasci che le eccezioni si diffondano e non puoi registrare nulla dall'interno del tuo metodo (ad esempio perché è una libreria e preferiresti che il codice chiamante gestisca le eccezioni e la registrazione):

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

AGGIORNAMENTO: Ho esaminato questo aspetto un po 'di più e ho trovato un ottimo post sul blog di qualcuno che ci ha chiaramente pensato più di me: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Fa un ulteriore passo avanti e combina le due eccezioni in una, che ho potuto vedere essere utile in alcuni casi.


1
+1 per il collegamento al blog. Inoltre, vorrei almeno registrare l' ignoreeccezione
Denis Kniazhev il

6

A partire da Java 7 non è più necessario chiudere esplicitamente le risorse in un blocco finalmente , ma è possibile utilizzare la sintassi try -with-resources. L'istruzione try-with-resources è un'istruzione try che dichiara una o più risorse. Una risorsa è un oggetto che deve essere chiuso dopo che il programma ha terminato con esso. L'istruzione try-with-resources assicura che ogni risorsa sia chiusa alla fine dell'istruzione. Qualsiasi oggetto che implementa java.lang.AutoCloseable, che include tutti gli oggetti che implementano java.io.Closeable, può essere utilizzato come risorsa.

Assumi il codice seguente:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Se si verifica un'eccezione, il metodo di chiusura verrà chiamato su ciascuna di queste tre risorse nell'ordine opposto in cui sono state create. Significa che il metodo di chiusura verrà chiamato prima per ResultSetm, quindi per l'istruzione e alla fine per l'oggetto Connection.

È anche importante sapere che tutte le eccezioni che si verificano quando i metodi di chiusura vengono chiamati automaticamente vengono soppresse. Queste eccezioni soppresse possono essere recuperate dal metodo getsuppressed () definito nella classe Throwable .

Fonte: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


Sembra incompleto che questa risposta non menzioni la differenza di comportamento tra questo approccio e il funzionamento del codice di esempio pubblicato dall'OP.
Nathan Hughes

2
l'utilizzo di try-with-resources genera un'eccezione alla chiusura se la parte nel blocco try si completa normalmente ma il metodo close no, a differenza di quanto fa il codice OP. raccomandarlo in sostituzione senza riconoscere il cambiamento di comportamento sembra potenzialmente fuorviante.
Nathan Hughes

Non genera un'eccezione, il metodo di chiusura viene automaticamente chiamato vengono soppressi.
Soroosh

2
prova il caso che ho descritto. try block si completa normalmente, close lancia qualcosa. e rileggi la pagina in cui hai pubblicato il collegamento, la soppressione si applica solo quando il blocco try lancia qualcosa.
Nathan Hughes

3

Ignorare le eccezioni che si verificano in un blocco "finalmente" è generalmente una cattiva idea a meno che non si sappia quali saranno quelle eccezioni e quali condizioni rappresenteranno. Nel try/finallymodello di utilizzo normale , il tryblocco pone le cose in uno stato che il codice esterno non si aspetta e il finallyblocco ripristina lo stato di quelle cose a quello che il codice esterno si aspetta. Il codice esterno che cattura un'eccezione generalmente si aspetta che, nonostante l'eccezione, tutto sia stato ripristinato in un filenormalstato. Ad esempio, supponiamo che un codice avvii una transazione e quindi provi ad aggiungere due record; il blocco "finalmente" esegue un'operazione di "rollback se non eseguito il commit". Un chiamante potrebbe essere preparato affinché si verifichi un'eccezione durante l'esecuzione della seconda operazione di "aggiunta" e potrebbe aspettarsi che, se rileva tale eccezione, il database si troverà nello stato in cui si trovava prima di tentare l'una o l'altra operazione. Se, tuttavia, si verifica una seconda eccezione durante il rollback, potrebbero verificarsi problemi se il chiamante fa qualche ipotesi sullo stato del database. L'errore di rollback rappresenta una grave crisi, che non dovrebbe essere rilevata dal codice che si aspetta una semplice eccezione "Impossibile aggiungere record".

La mia inclinazione personale sarebbe quella di fare in modo che un metodo finalmente catturi le eccezioni che si verificano e le racchiuda in una "CleanupFailedException", riconoscendo che tale fallimento rappresenta un problema importante e tale eccezione non dovrebbe essere presa alla leggera.


2

Una soluzione, se le due eccezioni sono due classi differenti

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Ma a volte non puoi evitare questo secondo tentativo di cattura. ad esempio per chiudere un flusso

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

Nel tuo caso, se hai usato un'istruzione "using", dovrebbe ripulire la risorsa.
Chuck Conway,

Colpa mia, presumo che sia C #.
Chuck Conway,

1

Perché vuoi evitare il blocco aggiuntivo? Dato che il blocco finalmente contiene operazioni "normali" che possono generare un'eccezione E vuoi che il blocco finalmente venga eseguito completamente, DEVI intercettare le eccezioni.

Se non ti aspetti che il blocco finalmente generi un'eccezione e non sai comunque come gestire l'eccezione (dovresti semplicemente eseguire il dump della traccia dello stack) lascia che l'eccezione si muova nello stack delle chiamate (rimuovi il try-catch dalla fine bloccare).

Se vuoi ridurre la digitazione potresti implementare un blocco try-catch esterno "globale", che rileverà tutte le eccezioni lanciate nei blocchi finalmente:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1 Anche per questo. Cosa succede se stai cercando di chiudere più risorse in un unico blocco finalmente? Se la chiusura della prima risorsa fallisce, le altre rimarranno aperte una volta generata l'eccezione.
Programmatore fuorilegge

Questo è il motivo per cui ho detto a Paul che DEVI intercettare le eccezioni se vuoi assicurarti che il blocco finalmente venga completato. Si prega di leggere l'intera risposta!
Eduard Wirch

1

Dopo molte considerazioni, trovo il seguente codice migliore:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Quel codice garantisce quanto segue:

  1. La risorsa viene liberata al termine del codice
  2. Le eccezioni generate alla chiusura della risorsa non vengono consumate senza elaborarle.
  3. Il codice non tenta di chiudere la risorsa due volte, non verrà creata alcuna eccezione non necessaria.

Potresti anche evitare di chiamare resource.close (); resource = null nel blocco try, ecco a cosa servono i blocchi. Si noti inoltre che non si gestiscono eccezioni lanciate mentre "si fa qualcosa di stravagante", cosa che in realtà, penso di preferire meglio, per gestire le eccezioni infrastrutturali a un livello di applicazione più alto nello stack.
Paul

Anche resource.close () potrebbe generare un'eccezione, ovvero quando lo scaricamento del buffer fallisce. Questa eccezione non dovrebbe mai essere utilizzata. Tuttavia, se si chiude il flusso a seguito di un'eccezione sollevata in precedenza, la risorsa deve essere chiusa silenziosamente ignorando l'eccezione e preservando la causa principale.
Grogi

0

Se puoi, dovresti provare per evitare la condizione di errore per cominciare.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

Inoltre dovresti probabilmente rilevare solo le eccezioni da cui puoi ripristinare, se non riesci a recuperare, lascia che si propaghi al livello superiore del tuo programma. Se non puoi testare una condizione di errore, dovrai circondare il tuo codice con un blocco try catch come hai già fatto (anche se consiglierei di catturare ancora errori specifici previsti).


Testare le condizioni di errore è in generale una buona pratica, semplicemente perché le eccezioni sono costose.
Dirk Vollmar

La "programmazione difensiva" è un paradigma antiquato. Il codice gonfio che risulta dal test di tutte le condizioni di errore alla fine causa più problemi di quanti ne risolva. Il TDD e la gestione delle eccezioni sono l'approccio moderno IMHO
Joe Soul-bringer,

@ Joe - Non sono in disaccordo con te sul test per tutte le condizioni di errore, ma a volte ha senso, soprattutto alla luce della differenza (normalmente) nel costo di un semplice controllo per evitare l'eccezione rispetto all'eccezione stessa.
Ken Henderson

1
-1 Qui, resource.Close () può generare un'eccezione. Se è necessario chiudere risorse aggiuntive, l'eccezione provocherà la restituzione della funzione e rimarranno aperte. Questo è lo scopo del secondo tentativo / cattura nell'OP.
Programmatore fuorilegge

@ Outlaw - ti manca il punto se Close genera un'eccezione e la risorsa è aperta, quindi catturando e sopprimendo l'eccezione, come posso correggere il problema? Ecco perché l'ho lasciato propagare (è abbastanza raro che io possa recuperare con esso ancora aperto).
Ken Henderson,

0

Potresti effettuare il refactoring in un altro metodo ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

Di solito lo faccio:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

Motivazione: se ho finito con la risorsa e l'unico problema che ho è chiuderla, non c'è molto che posso fare al riguardo. Non ha nemmeno senso uccidere l'intero thread se ho comunque finito con la risorsa.

Questo è uno dei casi in cui, almeno per me, è sicuro ignorare l'eccezione selezionata.

Fino ad oggi non ho avuto problemi a usare questo idioma.


Lo registrerei, nel caso in cui trovassi delle perdite in futuro. In questo modo sapresti da dove potrebbero (non) provenire
Egwor

@Egwor. Sono d'accordo con te. Questo era solo un breve smippet. Lo registro anche io e probabilmente uso una cattura è qualcosa che si potrebbe fare con l'eccezione :)
OscarRyz

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Lavoro fatto. Nessun test nullo. Cattura singola, includi acquisisci e rilascia eccezioni. Ovviamente puoi usare l'idioma Execute Around e devi scriverlo solo una volta per ogni tipo di risorsa.


5
Cosa succede se use (resource) genera l'eccezione A e quindi resource.release () genera l'eccezione B? L'eccezione A è persa ...
Darron

0

Passaggio Resourceda migliore risposta aCloseable

Implementazioni di flussi CloseableQuindi è possibile riutilizzare il metodo per tutti i flussi

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

Ho riscontrato una situazione simile in cui non potevo usare try con le risorse, ma volevo anche gestire l'eccezione proveniente dalla chiusura, non solo log e ignorarla come fa il meccanismo closeQuietly. nel mio caso non ho a che fare con un flusso di output, quindi l'errore alla chiusura è più interessante di un semplice flusso.

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
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.