Nihayet bloklarda İstisna atar


100

finallyBlok halinde atılan istisnaları ele almanın zarif bir yolu var mı ?

Örneğin:

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

Nasıl önleyebilirim try/ catchiçinde finallybloğun?

Yanıtlar:


72

Genelde böyle yapıyorum:

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

Başka yerde:

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

4
Evet, çok benzer bir deyim kullanıyorum. Ama bunun için bir işlev yaratmıyorum.
OscarRyz

9
Deyimi aynı sınıfta birkaç yerde kullanmanız gerekirse bir işlev kullanışlıdır.
Darron

Boş değer kontrolü gereksizdir. Kaynak boşsa, çağıran yöntem bozuksa düzeltilmelidir. Ayrıca, kaynak boşsa, muhtemelen günlüğe kaydedilmelidir. Aksi takdirde, sessizce göz ardı edilen potansiyel bir istisnayla sonuçlanır.
Dave Jarvis

14
Boş değer kontrolü her zaman gereksiz değildir. "Resource = new FileInputStream (" file.txt ")" yi denemenin ilk satırı olarak düşünün. Ayrıca, bu soru pek çok kişinin kullanmadığı görünüm odaklı programlama ile ilgili değildi. Bununla birlikte, İstisnanın sadece göz ardı edilmemesi gerektiği kavramı, en kompakt şekilde bir günlük ifadesi gösterilerek ele alındı.
Darron

1
Resource=> Closeable?
Dmitry Ginzburg

25

Genelde aşağıdaki closeQuietlyyöntemlerden birini kullanırım org.apache.commons.io.IOUtils:

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

3
Bu yöntemi Kapatılabilir genel statik boşluk kapat ile daha genel hale getirebilirsiniz: Sessizce (Kapatılabilir kapatılabilir) {
Peter Lawrey

6
Evet, Closeable güzeldir. Pek çok şeyin (JDBC kaynakları gibi) onu uygulamaması utanç verici.
Darron

22

Java 7 ve resourceuygulamaları kullanıyorsanız AutoClosable, bunu yapabilirsiniz (örnek olarak InputStream kullanarak):

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

8

Muhtemelen biraz fazla, ancak istisnaların ortaya çıkmasına izin veriyorsanız ve yönteminizin içinden hiçbir şey kaydedemiyorsanız yararlı olabilir (örneğin, bir kitaplık olduğundan ve arama kodunun istisnaları ve günlük kaydını işlemesine izin vermeyi tercih ettiğiniz için):

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

GÜNCELLEME: Buna biraz daha baktım ve bunu benden daha fazla düşünen birinden harika bir blog yazısı buldum: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Bir adım daha ileri giderek iki istisnayı tek bir istisnada birleştiriyor, bazı durumlarda faydalı olduğunu görebiliyordum.


1
Blog bağlantısı için +1. Ek olarak, en azından ignoreistisnayı
kaydederdim

6

Java 7'den itibaren, kaynakları bir nihayet bloğunda açıkça kapatmanız gerekmiyor, bunun yerine try -with-kaynakları sözdizimini kullanabilirsiniz. Kaynakla dene ifadesi, bir veya daha fazla kaynağı bildiren bir dene ifadesidir. Kaynak, programla bittikten sonra kapatılması gereken bir nesnedir. Kaynakları ile dene ifadesi, her kaynağın ifadenin sonunda kapatılmasını sağlar. Java.io.Closeable'ı uygulayan tüm nesneleri içeren java.lang.AutoCloseable'ı uygulayan herhangi bir nesne, kaynak olarak kullanılabilir.

Aşağıdaki kodu varsayalım:

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

Herhangi bir istisna olursa, bu üç kaynağın her biri için oluşturuldukları sıranın tersi sırayla kapat yöntemi çağrılacaktır. Bu, close yönteminin önce ResultSetm, ardından Statement ve sonunda Connection nesnesi için çağrılacağı anlamına gelir.

Kapatma yöntemleri otomatik olarak çağrıldığında oluşan istisnaların bastırıldığını bilmek de önemlidir. Bu bastırılmış istisnalar , Throwable sınıfında tanımlanan getsuppressed () yöntemi ile alınabilir .

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


Bu yanıtın, bu yaklaşım ile OP'nin yayınlanan örnek kodunun nasıl çalıştığı arasındaki davranış farkından bahsetmediği eksik görünüyor.
Nathan Hughes

2
try-with-resources kullanılması, try bloğundaki parça normal olarak tamamlanırsa, ancak OP kodunun yaptığından farklı olarak close yöntemi kapanmazsa, kapanışta bir istisna atar. Davranıştaki değişikliği kabul etmeden bir yedek olarak tavsiye etmek, potansiyel olarak yanıltıcı görünmektedir.
Nathan Hughes

Bir istisna atmaz, kapatma yöntemi otomatik olarak çağrılır bastırılır.
Soroosh

2
anlattığım vakayı dene. try bloğu normal şekilde tamamlanır, close bir şey fırlatır. ve bağlantıyı gönderdiğiniz sayfayı yeniden okursanız, bastırma yalnızca try bloğu bir şey attığında uygulanır.
Nathan Hughes

3

Bir 'nihayet' blokta meydana gelen istisnaları göz ardı etmek, bu istisnaların ne olacağını ve hangi koşulları temsil edeceklerini bilmedikçe genellikle kötü bir fikirdir . Normal try/finallykullanım modelinde, tryblok şeyleri, dış kodun beklemeyeceği bir duruma yerleştirir ve finallyblok , bu şeylerin durumunu, dış kodun beklediği şekilde geri yükler. Bir istisnayı yakalayan dış kod genellikle istisnaya rağmen her şeyin birnormaldurum. Örneğin, bir kodun bir işlemi başlattığını ve ardından iki kayıt eklemeye çalıştığını varsayalım; "nihayet" bloğu, "işlenmemişse geri dönme" işlemi gerçekleştirir. Bir arayıcı, ikinci "ekleme" işleminin yürütülmesi sırasında meydana gelen bir istisnaya hazırlanabilir ve böyle bir istisnayı yakalarsa, veritabanının her iki işlemden biri denenmeden önceki durumda olmasını bekleyebilir. Bununla birlikte, geri alma sırasında ikinci bir istisna oluşursa, arayan kişi veritabanı durumu hakkında herhangi bir varsayımda bulunursa kötü şeyler olabilir. Geri alma başarısızlığı büyük bir krizi temsil eder - yalnızca "Kayıt eklenemedi" istisnası bekleyen kod tarafından yakalanmaması gerekir.

Benim kişisel eğilimim, ortaya çıkan istisnaları yakalayan bir nihayet yöntemine sahip olmak ve bunları bir "CleanupFailedException" içine almak, böylesi bir başarısızlığın büyük bir sorunu temsil ettiğini ve böyle bir istisnanın hafife alınmaması gerektiğidir.


2

İki İstisna iki farklı sınıfsa bir çözüm

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

Ancak bazen bu ikinci denemeden kaçınamazsınız. örneğin bir akışı kapatmak için

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

Sizin durumunuzda bir "using" ifadesi kullandıysanız, kaynağı temizlemelidir.
Chuck Conway

Benim hatam, C # olduğunu varsayıyorum.
Chuck Conway

1

Ek bloktan neden kaçınmak istiyorsunuz? Nihayet bloğu, bir istisna atabilecek "normal" işlemler içerdiğinden VE final bloğunun tamamen çalışmasını istediğinizden, istisnaları yakalamanız GEREKİR.

Nihayet bloğunun bir istisna atmasını beklemiyorsanız ve yine de istisnayı nasıl ele alacağınızı bilmiyorsanız (sadece yığın izlemeyi dökersiniz) istisnanın çağrı yığınını yukarı kaldırmasına izin verin (try-catch'i sondan kaldırın. blok).

Yazmayı azaltmak istiyorsanız, nihayet bloklarda atılan tüm istisnaları yakalayacak "global" bir dış try-catch bloğu uygulayabilirsiniz:

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

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

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

2
-1 Bunun için de. Ya tek bir nihayet blokta birden çok kaynağı kapatmaya çalışıyorsanız? İlk kaynağın kapatılması başarısız olursa, istisna atıldıktan sonra diğerleri açık kalacaktır.
Outlaw Programmer

Bu yüzden Paul'e, nihayet bloğun tamamlandığından emin olmak istiyorsanız, istisnaları yakalamanız gerektiğini söyledim. Lütfen BÜTÜN cevabı okuyun!
Eduard Wirch

1

Çok düşündükten sonra, aşağıdaki kodu en iyi buluyorum:

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

Bu kod aşağıdakileri garanti eder:

  1. Kod bittiğinde kaynak serbest bırakılır
  2. Kaynak kapatılırken atılan istisnalar işlenmeden tüketilmez.
  3. Kod, kaynağı iki kez kapatmaya çalışmaz, gereksiz bir istisna yaratılmaz.

Ayrıca resource.close () 'yi çağırmaktan da kaçınabilirsiniz; try bloğunda resource = null, nihayet bloklar bunun içindir. Ayrıca, "süslü bir şey yaparken" ortaya çıkan istisnalarla başa çıkmadığınızı unutmayın, ki bu aslında altyapı istisnalarını yığının altındaki daha yüksek bir uygulama seviyesinde ele almayı daha çok tercih ederim.
Paul

Resource.close () atabilir ve istisna da yapabilir - yani tampon boşaltma başarısız olduğunda. Bu istisna asla tüketilmemelidir. Bununla birlikte, daha önce ortaya konan istisnanın bir sonucu olarak akışı kapatıyorsanız, istisna göz ardı edilerek ve temel neden korunarak kaynak sessizce kapatılmalıdır.
Grogi

0

Eğer yapabilirseniz, hata durumundan kaçınmak için test etmelisiniz.

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

Ayrıca, muhtemelen sadece kurtarabileceğiniz istisnaları yakalamalısınız, eğer kurtaramazsanız, programınızın en üst seviyesine yayılmasına izin verin. Bir hata koşulunu test edemezseniz, kodunuzu zaten yaptığınız gibi bir deneme yakalama bloğu ile çevrelemeniz gerekir (yine de belirli, beklenen hataları yakalamanızı tavsiye ederim).


Hata koşullarının test edilmesi genellikle iyi bir uygulamadır çünkü istisnalar pahalıdır.
Dirk Vollmar

"Defansif Programlama" modası geçmiş bir paradigmadır. Tüm hata durumlarının test edilmesinden kaynaklanan şişirilmiş kod, sonunda çözdüğünden daha fazla soruna neden olur. TDD ve kullanım istisnaları, modern yaklaşımdır IMHO
Joe Soul-bringer

@Joe - Tüm hata koşullarını test etme konusunda size katılmıyorum, ancak bazen, istisnanın kendisine karşı istisnayı önlemek için basit bir kontrolün maliyetindeki fark (normalde) ışığında mantıklı geliyor.
Ken Henderson

1
-1 Burada, resource.Close () bir istisna atabilir. Ek kaynakları kapatmanız gerekirse, istisna işlevin geri dönmesine neden olur ve bunlar açık kalır. OP'deki ikinci deneme / yakalama işleminin amacı budur.
Outlaw Programmer

@Outlaw - Kapat bir istisna atarsa ​​ve kaynak açıksa, istisnayı yakalayıp bastırarak sorunu nasıl düzeltirim? Bu yüzden yayılmasına izin verdim (hala açıkken kurtarabilmem oldukça nadirdir).
Ken Henderson

0

Bunu başka bir yöntemde yeniden düzenleyebilirsiniz ...

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

Genellikle bunu yaparım:

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

Gerekçe: Kaynakla işim bittiyse ve sahip olduğum tek sorun onu kapatmaksa, bu konuda yapabileceğim pek bir şey yok. Zaten kaynakla işim bittiğinde tüm iş parçacığını öldürmek de mantıklı değil.

Bu, en azından benim için, kontrol edilen istisnayı göz ardı etmenin güvenli olduğu durumlardan biridir.

Bugüne kadar bu deyimi kullanırken herhangi bir sorun yaşamadım.


Gelecekte bazı sızıntılar bulursan diye kaydederim. Bu şekilde nereden geldiklerini (gelmediklerini)
bilirsiniz

@Egwor. Size katılıyorum. Bu sadece hızlı bir smippetti. Ben de
kaydediyorum

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

İş bitmiş. Boş test yok. Tek yakalama, alma ve bırakma istisnalarını içerir. Elbette Execute Around deyimini kullanabilirsiniz ve her kaynak türü için bunu yalnızca bir kez yazmanız gerekir.


5
Use (resource), Exception A ve ardından resource.release () B istisnası atarsa ​​ne olur? İstisna A kayboldu ...
Darron

0

Değişen Resourcegelen en iyi yanıt içinCloseable

Akışlar uygular CloseableBöylece tüm akışlar için yöntemi yeniden kullanabilirsiniz.

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

0

Kaynaklarla denemeyi kullanamadığım benzer bir durumla karşılaştım ama aynı zamanda closeQuietly mekanizmasının yaptığı gibi günlük tutmayı ve onu görmezden gelmeyi değil, yakınlardan gelen istisnayı da ele almak istedim. benim durumumda aslında bir çıktı akışıyla uğraşmıyorum, bu nedenle kapanıştaki başarısızlık basit bir akıştan daha ilgi çekicidir.

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.