melempar Exception pada blok terakhir

100

Adakah cara elegan untuk menangani pengecualian yang dilanggar finally?

Sebagai contoh:

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

Bagaimana Anda menghindari try/ catchdi finallyblok?

Paul
sumber

Jawaban:

72

Saya biasanya melakukannya seperti ini:

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

Di tempat lain:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}
Darron
sumber
4
Ya, saya menggunakan idiom yang sangat mirip. Tapi saya tidak membuat fungsi untuk itu.
OscarRyz
9
Fungsi berguna jika Anda perlu menggunakan idiom di beberapa tempat di kelas yang sama.
Darron
Pemeriksaan null berlebihan. Jika sumber daya adalah null, maka metode pemanggilan yang rusak harus diperbaiki. Juga, jika sumber daya adalah null, itu mungkin harus dicatat. Jika tidak, maka potensi pengecualian akan diabaikan secara diam-diam.
Dave Jarvis
14
Pemeriksaan null tidak selalu berlebihan. Pikirkan "resource = new FileInputStream (" file.txt ")" sebagai baris pertama percobaan. Selain itu, pertanyaan ini bukan tentang pemrograman berorientasi aspek yang tidak digunakan banyak orang. Namun, konsep bahwa Exception tidak boleh diabaikan begitu saja ditangani dengan menunjukkan pernyataan log.
Darron
1
Resource=> Closeable?
Dmitry Ginzburg
25

Saya biasanya menggunakan salah satu closeQuietlymetode dalam org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}
CJS
sumber
3
Anda dapat membuat metode ini lebih umum dengan closeQuietly public static void closeQuietly (Closeable closeable) {
Peter Lawrey
6
Ya, Closeable bagus. Sayang sekali banyak hal (seperti sumber daya JDBC) tidak menerapkannya.
Darron
22

Jika Anda menggunakan Java 7, dan resourceimplementasinya AutoClosable, Anda dapat melakukan ini (menggunakan InputStream sebagai contoh):

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

Bisa dibilang agak berlebihan, tetapi mungkin berguna jika Anda membiarkan pengecualian muncul dan Anda tidak dapat mencatat apa pun dari dalam metode Anda (misalnya karena ini adalah perpustakaan dan Anda lebih suka membiarkan kode panggilan menangani pengecualian dan pencatatan):

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

PEMBARUAN: Saya melihat ini sedikit lebih banyak dan menemukan entri blog yang bagus dari seseorang yang secara jelas telah memikirkan hal ini lebih dari saya: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Dia melangkah lebih jauh dan menggabungkan dua pengecualian menjadi satu, yang menurut saya berguna dalam beberapa kasus.

MB.
sumber
1
+1 untuk tautan blog. Selain itu, saya setidaknya akan mencatat ignorepengecualian
Denis Kniazhev
6

Mulai dari Java 7, Anda tidak perlu lagi menutup sumber daya secara eksplisit di blok akhirnya, sebagai gantinya Anda dapat menggunakan sintaks try -with-resources. Pernyataan try-with-resources adalah pernyataan percobaan yang mendeklarasikan satu atau lebih resource. Sumber daya adalah objek yang harus ditutup setelah program selesai dengannya. Pernyataan coba-dengan-sumber daya memastikan bahwa setiap sumber daya ditutup di akhir pernyataan. Objek apa pun yang mengimplementasikan java.lang.AutoCloseable, yang menyertakan semua objek yang mengimplementasikan java.io.Closeable, bisa digunakan sebagai sumber daya.

Asumsikan kode berikut:

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

Jika terjadi pengecualian, metode penutupan akan dipanggil pada masing-masing dari ketiga sumber daya ini dalam urutan yang berlawanan saat dibuat. Ini berarti metode tutup akan dipanggil pertama untuk ResultSetm kemudian Pernyataan dan di akhir untuk objek Connection.

Penting juga untuk mengetahui bahwa pengecualian apa pun yang terjadi saat metode tutup dipanggil secara otomatis akan disembunyikan. Pengecualian yang disembunyikan ini bisa diambil dengan metode getsuppressed () yang ditentukan di kelas Throwable .

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

Soroosh
sumber
Tampaknya tidak lengkap bahwa jawaban ini tidak menyebutkan perbedaan perilaku antara pendekatan ini dan cara kerja kode contoh yang diposting OP.
Nathan Hughes
2
menggunakan try-with-resources melempar pengecualian saat tutup jika bagian dalam blok percobaan selesai secara normal tetapi metode tutup tidak, tidak seperti yang dilakukan kode OP. merekomendasikannya sebagai pengganti tanpa mengakui perubahan perilaku tampaknya berpotensi menyesatkan.
Nathan Hughes
Itu tidak melempar pengecualian, metode dekat secara otomatis dipanggil ditekan.
Soroosh
2
coba kasus yang saya jelaskan. mencoba blok selesai secara normal, dekat melempar sesuatu. dan membaca ulang halaman yang Anda posting linknya, penekanan hanya berlaku ketika blok percobaan melempar sesuatu.
Nathan Hughes
3

Mengabaikan pengecualian yang terjadi dalam blok 'akhirnya' umumnya merupakan ide yang buruk kecuali seseorang mengetahui apa pengecualian itu dan kondisi apa yang akan mereka wakili. Dalam try/finallypola penggunaan normal , tryblok menempatkan sesuatu ke dalam kondisi yang tidak diharapkan oleh kode luar, dan finallyblok mengembalikan status hal-hal itu ke apa yang diharapkan kode luar. Kode luar yang menangkap pengecualian umumnya akan mengharapkan bahwa, meskipun ada pengecualian, semuanya telah dikembalikan ke filenormalnegara. Misalnya, beberapa kode memulai transaksi dan kemudian mencoba menambahkan dua catatan; blok "akhirnya" melakukan operasi "rollback jika tidak dilakukan". Pemanggil mungkin bersiap untuk pengecualian terjadi selama eksekusi operasi "tambah" kedua, dan mungkin berharap bahwa jika ia menangkap pengecualian seperti itu, database akan berada dalam keadaan sebelum salah satu operasi dicoba. Namun, jika pengecualian kedua terjadi selama rollback, hal buruk dapat terjadi jika pemanggil membuat asumsi apa pun tentang status database. Kegagalan rollback merupakan krisis besar - krisis yang seharusnya tidak tertangkap oleh kode yang mengharapkan pengecualian hanya "Gagal menambahkan catatan".

Kecenderungan pribadi saya akan memiliki metode akhirnya menangkap pengecualian yang terjadi dan membungkusnya dalam "CleanupFailedException", menyadari bahwa kegagalan tersebut merupakan masalah besar dan pengecualian tersebut tidak boleh dianggap enteng.

supercat
sumber
2

Salah satu solusinya, jika dua Pengecualian adalah dua kelas yang berbeda

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

Tetapi terkadang Anda tidak dapat menghindari coba-coba kedua ini. misalnya untuk menutup aliran

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
sumber
Dalam kasus Anda, jika Anda menggunakan pernyataan "using", pernyataan itu harus membersihkan sumber daya.
Chuck Conway
Saya buruk, saya berasumsi itu C #.
Chuck Conway
1

Mengapa Anda ingin menghindari blok tambahan? Karena blok terakhir berisi operasi "normal" yang dapat memunculkan pengecualian DAN Anda ingin blok akhirnya berjalan sepenuhnya, Anda HARUS menangkap pengecualian.

Jika Anda tidak mengharapkan blok akhirnya memunculkan pengecualian dan Anda tidak tahu bagaimana menangani pengecualian tersebut (Anda hanya akan membuang jejak tumpukan) biarkan pengecualian menggelembung ke tumpukan panggilan (hapus coba-tangkap dari blok).

Jika Anda ingin mengurangi pengetikan, Anda dapat menerapkan blok try-catch luar "global", yang akan menangkap semua pengecualian yang dilemparkan pada blok terakhir:

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

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

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}
Eduard Wirch
sumber
2
-1 Untuk yang ini juga. Bagaimana jika Anda mencoba menutup banyak sumber daya dalam satu blok terakhir? Jika menutup sumber pertama gagal, yang lain akan tetap terbuka setelah pengecualian dilemparkan.
Outlaw Programmer
Inilah sebabnya saya memberi tahu Paul bahwa Anda HARUS menangkap pengecualian jika Anda ingin memastikan blok terakhir selesai. Silakan baca jawaban SELURUH!
Eduard Wirch
1

Setelah banyak pertimbangan, saya menemukan kode berikut yang terbaik:

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

Kode itu menjamin hal-hal berikut:

  1. Sumber daya dibebaskan setelah kode selesai
  2. Pengecualian yang diberikan saat menutup sumber daya tidak dikonsumsi tanpa memprosesnya.
  3. Kode tidak mencoba menutup sumber daya dua kali, tidak ada pengecualian yang tidak perlu akan dibuat.
Grogi
sumber
Anda juga bisa menghindari memanggil resource.close (); resource = null di blok percobaan, itulah gunanya blok. Perhatikan juga bahwa Anda tidak menangani pengecualian apa pun yang dilemparkan saat "melakukan sesuatu yang mewah", yang sebenarnya, menurut saya, lebih baik, menangani pengecualian infrastruktur pada tingkat aplikasi yang lebih tinggi di bawah tumpukan.
Paul
Resource.close () mungkin akan menampilkan dan pengecualian juga - yaitu saat buffer flush gagal. Pengecualian ini tidak boleh dikonsumsi. Namun, jika menutup aliran sebagai akibat dari pengecualian yang dimunculkan sebelumnya, sumber daya harus ditutup secara diam-diam dengan mengabaikan pengecualian dan mempertahankan akar masalahnya.
Grogi
0

Jika Anda bisa, Anda harus menguji untuk menghindari kondisi kesalahan untuk memulai.

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

Juga Anda mungkin hanya menangkap pengecualian yang dapat Anda pulihkan, jika Anda tidak dapat memulihkan maka biarkan menyebar ke tingkat atas program Anda. Jika Anda tidak dapat menguji kondisi kesalahan bahwa Anda harus mengelilingi kode Anda dengan blok coba tangkap seperti yang sudah Anda lakukan (meskipun saya akan merekomendasikan untuk tetap menangkap kesalahan spesifik yang diharapkan).

Ken Henderson
sumber
Menguji kondisi kesalahan secara umum merupakan praktik yang baik, hanya karena pengecualian itu mahal.
Dirk Vollmar
"Pemrograman Defensif" adalah paradigma kuno. Kode yang membengkak yang dihasilkan dari pengujian untuk semua kondisi kesalahan pada akhirnya menyebabkan lebih banyak masalah daripada penyelesaiannya. TDD dan pengecualian penanganannya adalah pendekatan modern IMHO
Joe Soul-bringer
@Joe - Saya tidak setuju Anda dalam pengujian untuk semua kondisi kesalahan, tetapi kadang-kadang masuk akal, terutama mengingat perbedaan (biasanya) dalam biaya pemeriksaan sederhana untuk menghindari pengecualian versus pengecualian itu sendiri.
Ken Henderson
1
-1 Di sini, resource.Close () bisa memunculkan pengecualian. Jika Anda perlu menutup resource tambahan, pengecualian akan menyebabkan fungsi tersebut kembali dan akan tetap terbuka. Itulah tujuan dari try / catch kedua di OP.
Outlaw Programmer
@Outlaw - Anda kehilangan maksud saya jika Tutup melontarkan pengecualian, dan sumber daya terbuka kemudian dengan menangkap dan menekan pengecualian, bagaimana cara memperbaiki masalah? Oleh karena itu mengapa saya membiarkannya menyebar (cukup jarang saya dapat memulihkan dengan itu masih terbuka).
Ken Henderson
0

Anda dapat mengubah ini menjadi metode lain ...

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
sumber
0

Saya biasanya melakukan ini:

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

Rasional: Jika saya sudah selesai dengan sumber daya dan satu-satunya masalah yang saya miliki adalah menutupnya, tidak banyak yang bisa saya lakukan. Tidak masuk akal juga untuk membunuh seluruh utas jika saya sudah selesai dengan sumber daya.

Ini adalah salah satu kasus ketika setidaknya bagi saya, aman untuk mengabaikan pengecualian yang dicentang itu.

Sampai hari ini saya tidak memiliki masalah menggunakan idiom ini.

OscarRyz
sumber
Saya akan mencatatnya, kalau-kalau Anda menemukan beberapa kebocoran di masa depan. Dengan begitu Anda akan tahu dari mana mereka mungkin (bukan) berasal
Egwor
@Egor. Saya setuju denganmu. Ini hanya tipuan singkat. Saya mencatatnya juga dan mungkin menggunakan tangkapan adalah sesuatu yang bisa dilakukan dengan pengecualian :)
OscarRyz
0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Pekerjaan selesai. Tidak ada tes nol. Tangkapan tunggal, termasuk memperoleh dan melepaskan pengecualian. Tentu saja Anda dapat menggunakan idiom Execute Around dan hanya perlu menuliskannya sekali untuk setiap jenis resource.

Tom Hawtin - tackline
sumber
5
Bagaimana jika use (resource) melontarkan Exception A dan resource.release () melontarkan pengecualian B? Pengecualian A hilang ...
Darron
0

Berubah Resourcedari jawaban terbaik menjadiCloseable

Aliran mengimplementasikan CloseableDengan demikian Anda dapat menggunakan kembali metode ini untuk semua aliran

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

Saya mengalami situasi serupa di mana saya tidak dapat menggunakan try dengan sumber daya tetapi saya juga ingin menangani pengecualian yang datang dari dekat, tidak hanya mencatat dan mengabaikannya seperti mekanisme closeQuietly. dalam kasus saya, saya tidak benar-benar berurusan dengan aliran keluaran, jadi kegagalan saat menutup lebih menarik daripada aliran sederhana.

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
sumber