rzuca wyjątek w ostatecznie blokach

100

Czy istnieje elegancki sposób obsługi wyjątków, które są wrzucane w finallybloku?

Na przykład:

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

Jak uniknąć try/ catchw finallybloku?

Paweł
źródło

Odpowiedzi:

72

Zwykle robię to tak:

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

Gdzie indziej:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}
Darron
źródło
4
Tak, używam bardzo podobnego idiomu. Ale nie tworzę funkcji do tego.
OscarRyz
9
Funkcja jest przydatna, jeśli chcesz użyć idiomu w kilku miejscach w tej samej klasie.
Darron
Sprawdzanie wartości null jest zbędne. Jeśli zasób był pusty, należy naprawić metodę wywołującą. Ponadto, jeśli zasób jest pusty, prawdopodobnie powinno to zostać zarejestrowane. W przeciwnym razie spowoduje to dyskretne zignorowanie potencjalnego wyjątku.
Dave Jarvis
14
Sprawdzanie wartości null nie zawsze jest zbędne. Pomyśl o „resource = new FileInputStream („ file.txt ”)” jako o pierwszym wierszu próby. Również to pytanie nie dotyczyło programowania aspektowego, z którego wiele osób nie korzysta. Jednak koncepcja, zgodnie z którą wyjątek nie powinien być po prostu ignorowana, została najbardziej zwięzła obsłużona przez wyświetlenie instrukcji dziennika.
Darron
1
Resource=> Closeable?
Dmitry Ginzburg
25

Zwykle używam jednej z closeQuietlymetod w org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}
CJS
źródło
3
Możesz uczynić tę metodę bardziej ogólną, używając Closeable public static void closeQuietly (zamykane, zamykane) {
Peter Lawrey,
6
Tak, zamykanie jest fajne. Szkoda, że ​​wiele rzeczy (np. Zasoby JDBC) go nie implementuje.
Darron
22

Jeśli używasz Java 7 i resourceimplementujesz AutoClosable, możesz to zrobić (na przykładzie InputStream):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
Kevin Wong
źródło
8

Prawdopodobnie trochę przesadzone, ale może być przydatne, jeśli pozwalasz wyjątkom pojawiać się i nie możesz rejestrować niczego z poziomu swojej metody (np. Ponieważ jest to biblioteka i wolisz, aby kod wywołujący obsługiwał wyjątki i rejestrowanie):

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).
            }
        }
    }
}

AKTUALIZACJA: przyjrzałem się temu nieco dokładniej i znalazłem świetny post na blogu od kogoś, kto wyraźnie myślał o tym więcej niż ja: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Idzie o krok dalej i łączy dwa wyjątki w jeden, co wydaje mi się przydatne w niektórych przypadkach.

MB.
źródło
1
+1 dla linku do bloga. Dodatkowo ignore
zapisałbym
6

Jak Java 7 nie ma już potrzeby, aby wyraźnie bliskich zasobów w końcu zablokować można użyć zamiast spróbować składni -z-zasobów. Instrukcja try-with-resources to instrukcja try, która deklaruje jeden lub więcej zasobów. Zasób to obiekt, który musi zostać zamknięty po zakończeniu działania programu. Instrukcja try-with-resources gwarantuje, że każdy zasób zostanie zamknięty na końcu instrukcji. Jako zasób można użyć dowolnego obiektu, który implementuje java.lang.AutoCloseable, który zawiera wszystkie obiekty implementujące java.io.Closeable.

Załóżmy następujący kod:

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

Jeśli wystąpi jakikolwiek wyjątek, metoda close zostanie wywołana dla każdego z tych trzech zasobów w odwrotnej kolejności, w jakiej zostały utworzone. Oznacza to, że metoda close zostanie wywołana najpierw dla ResultSetm, następnie dla instrukcji i na końcu dla obiektu Connection.

Ważne jest również, aby wiedzieć, że wszelkie wyjątki, które występują, gdy metody close są wywoływane automatycznie, są pomijane. Te wyłączone wyjątki można pobrać za pomocą metody getsuppressed () zdefiniowanej w klasie Throwable .

Źródło: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

Soroosh
źródło
Wydaje się niepełne, że ta odpowiedź nie wspomina o różnicy w zachowaniu między tym podejściem a sposobem działania opublikowanego przykładowego kodu PO.
Nathan Hughes
2
użycie try-with-resources zgłasza wyjątek przy zamknięciu, jeśli część w bloku try kończy się normalnie, ale metoda close nie, w przeciwieństwie do tego, co robi kod OP. Rekomendowanie go jako zamiennika bez uznania zmiany w zachowaniu wydaje się potencjalnie mylące.
Nathan Hughes
Nie zgłasza wyjątku, metoda close jest wywoływana automatycznie i są pomijane.
Soroosh,
2
spróbuj opisanego przeze mnie przypadku. try block kończy się normalnie, close coś rzuca. i ponownie przeczytaj stronę, na której zamieściłeś link, pomijanie ma zastosowanie tylko wtedy, gdy blok try coś rzuca.
Nathan Hughes
3

Ignorowanie wyjątków, które występują w bloku „ostatecznie”, jest generalnie złym pomysłem, chyba że wiadomo, jakie będą te wyjątki i jakie warunki będą reprezentować. W normalnym try/finallywzorcu użycia tryblok umieszcza rzeczy w stanie, którego zewnętrzny kod nie będzie oczekiwał, afinally blok przywraca stan tych rzeczy do tego, czego oczekuje kod zewnętrzny. Kod zewnętrzny, który wyłapuje wyjątek, będzie generalnie oczekiwać, że pomimo wyjątku wszystko zostało przywrócone do plikunormalstan. Na przykład załóżmy, że jakiś kod rozpoczyna transakcję, a następnie próbuje dodać dwa rekordy; blok „na końcu” wykonuje operację „wycofywania zmian, jeśli nie zostały zatwierdzone”. Obiekt wywołujący może być przygotowany na wystąpienie wyjątku podczas wykonywania drugiej operacji „dodawania” i może oczekiwać, że jeśli przechwyci taki wyjątek, baza danych będzie w stanie, w jakim znajdowała się przed próbą wykonania którejkolwiek z operacji. Jeśli jednak podczas wycofywania wystąpi drugi wyjątek, mogą się zdarzyć złe rzeczy, jeśli wywołujący podejmie jakiekolwiek założenia dotyczące stanu bazy danych. Błąd wycofywania zmian stanowi poważny problem kryzys - taki, który nie powinien zostać przechwycony przez kod oczekujący jedynie wyjątku „Nie udało się dodać rekordu”.

Osobiście wolałbym mieć ostateczną metodę wychwytującą wyjątki, które występują, i umieszczania ich w „wyjątku CleanupFailedException”, uznając, że taka awaria stanowi poważny problem i takiego wyjątku nie należy lekceważyć.

supercat
źródło
2

Jedno rozwiązanie, jeśli dwa wyjątki to dwie różne klasy

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

Ale czasami nie da się uniknąć tej drugiej próby. np. do zamykania strumienia

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 */ }
 }
Pierre
źródło
W twoim przypadku, jeśli użyłeś instrukcji „using”, powinno to wyczyścić zasób.
Chuck Conway
Mój błąd, zakładam, że to C #.
Chuck Conway
1

Dlaczego chcesz uniknąć dodatkowego bloku? Ponieważ ostatni blok zawiera "normalne" operacje, które mogą zgłosić wyjątek ORAZ chcesz, aby ostatni blok działał całkowicie, MUSISZ przechwytywać wyjątki.

Jeśli nie spodziewasz się, że ostatni blok wyrzuci wyjątek, a mimo to nie wiesz, jak obsłużyć wyjątek (po prostu zrzuciłbyś ślad stosu), pozwól wyjątkowi wypłynąć na stos wywołań (usuń try-catch z końcowego blok).

Jeśli chcesz ograniczyć pisanie, możesz zaimplementować „globalny” zewnętrzny blok try-catch, który będzie przechwytywał wszystkie wyjątki wrzucane do bloków final:

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

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

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}
Eduard Wirch
źródło
2
-1 dla tego też. Co się stanie, jeśli próbujesz zamknąć wiele zasobów w jednym bloku końcowym? Jeśli zamknięcie pierwszego zasobu nie powiedzie się, pozostałe pozostaną otwarte po zgłoszeniu wyjątku.
Outlaw Programmer
Dlatego powiedziałem Paulowi, że MUSISZ przechwytywać wyjątki, jeśli chcesz się upewnić, że ostatni blok się zakończy. Przeczytaj CAŁĄ odpowiedź!
Eduard Wirch
1

Po wielu przemyśleniach uważam, że następujący kod jest najlepszy:

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
        }
}

Ten kod gwarantuje, że:

  1. Zasób jest zwalniany po zakończeniu kodu
  2. Wyjątki zgłaszane podczas zamykania zasobu nie są zużywane bez ich przetwarzania.
  3. Kod nie próbuje dwukrotnie zamykać zasobu, nie zostanie utworzony żaden niepotrzebny wyjątek.
Grogi
źródło
Możesz także uniknąć wywoływania funkcji resource.close (); resource = null w bloku try, do tego służą ostatecznie bloki. Zauważ również, że nie obsługujesz żadnych wyjątków wyrzucanych podczas "robienia czegoś wymyślnego", co w rzeczywistości, moim zdaniem, wolę lepiej, aby obsłużyć wyjątki infrastrukturalne na wyższym poziomie aplikacji niżej.
Paul
Zasób.close () może również zgłosić i wyjątek - np. Gdy opróżnienie bufora nie powiedzie się. Ten wyjątek nie powinien być nigdy używany. Jeśli jednak zamykasz strumień w wyniku wcześniej zgłoszonego wyjątku, zasób powinien zostać cicho zamknięty, ignorując wyjątek i zachowując główną przyczynę.
Grogi
0

Jeśli możesz, najpierw przetestuj, aby uniknąć błędu.

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
  }
}

Powinieneś także prawdopodobnie łapać tylko wyjątki, z których możesz odzyskać, jeśli nie możesz odzyskać, pozwól mu rozprzestrzenić się na najwyższy poziom twojego programu. Jeśli nie możesz przetestować stanu błędu, będziesz musiał otoczyć swój kod blokiem try catch, tak jak już to zrobiłeś (chociaż zalecałbym nadal wychwytywanie określonych, oczekiwanych błędów).

Ken Henderson
źródło
Testowanie warunków błędu jest ogólnie dobrą praktyką, po prostu dlatego, że wyjątki są drogie.
Dirk Vollmar
„Programowanie obronne” to przestarzały paradygmat. Nadęty kod, który jest wynikiem testowania wszystkich błędów, ostatecznie powoduje więcej problemów niż rozwiązuje. TDD i obsługa wyjątków to nowoczesne podejście IMHO
Joe Soul-bringer
@Joe - Nie sprzeciwiam się testowaniu wszystkich warunków błędów, ale czasami ma to sens, zwłaszcza w świetle różnicy (zwykle) w koszcie prostego sprawdzenia w celu uniknięcia wyjątku w porównaniu z samym wyjątkiem.
Ken Henderson
1
-1 Tutaj resource.Close () może zgłosić wyjątek. Jeśli musisz zamknąć dodatkowe zasoby, wyjątek spowodowałby powrót funkcji i pozostaną one otwarte. Taki jest cel drugiej próby / złapania w PO.
Outlaw Programmer
@Outlaw - nie rozumiesz, jeśli Close zgłasza wyjątek, a zasób jest otwarty, przechwytując i pomijając wyjątek. Jak naprawić problem? Dlatego pozwalam mu się rozmnażać (jest to dość rzadkie, że mogę wyzdrowieć, gdy jest nadal otwarty).
Ken Henderson
0

Możesz zmienić to na inną metodę ...

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(); 
    }
  }
}
Sam Saffron
źródło
0

Zwykle robię to:

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

Uzasadnienie: Jeśli skończę z zasobem i jedynym problemem, jaki mam, jest jego zamknięcie, niewiele mogę z tym zrobić. Nie ma też sensu zabijanie całego wątku, jeśli i tak skończę z zasobem.

Jest to jeden z przypadków, w których przynajmniej dla mnie można bezpiecznie zignorować ten zaznaczony wyjątek.

Do dziś nie miałem żadnego problemu z używaniem tego idiomu.

OscarRyz
źródło
Zarejestruję to, na wypadek gdybyś w przyszłości znalazł jakieś wycieki. W ten sposób wiesz, skąd mogą (nie) pochodzić
Egwor
@Egwor. Zgadzam się z Tobą. To był tylko szybki smippet. Ja też to loguję i chyba skorzystam z haczyka to da się zrobić z wyjątkiem :)
OscarRyz
0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Zadanie wykonane. Brak testów zerowych. Pojedynczy połów obejmuje wyjątki dotyczące pozyskiwania i wypuszczania. Oczywiście możesz użyć idiomu Wykonaj dookoła i musisz napisać go tylko raz dla każdego typu zasobu.

Tom Hawtin - haczyk
źródło
5
Co się stanie, jeśli use (zasób) zgłosi wyjątek A, a następnie resource.release () zgłosi wyjątek B? Wyjątek A jest przegrany ...
Darron
0

Zmiana Resourcez najlepszej odpowiedzi doCloseable

Implementacje strumieniowe CloseableW ten sposób możesz ponownie użyć metody dla wszystkich strumieni

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}
Ryan
źródło
0

Spotkałem się z podobną sytuacją, w której nie mogłem użyć try z zasobami, ale chciałem też obsłużyć wyjątek pochodzący z zamknięcia, a nie tylko zalogować się i zignorować, jak robi to mechanizm closeQuietly. w moim przypadku nie mam do czynienia ze strumieniem wyjściowym, więc awaria przy zamknięciu jest bardziej interesująca niż zwykły strumień.

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);
}
David Bradley
źródło