Pengecualian Java tidak tertangkap?

170

Saya punya masalah teoritis kecil dengan konstruksi try-catch.

Saya mengikuti ujian praktis kemarin tentang Jawa dan saya tidak mengerti contoh berikut:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

Pertanyaannya adalah "seperti apa hasilnya nanti?"

Saya cukup yakin itu akan menjadi AB2C3, TAPI mengejutkan, itu tidak benar.

Jawaban yang tepat adalah ABC3 (diuji dan benar-benar seperti itu).

Pertanyaan saya adalah, kemana perginya Exception ("2")?

Kousalik
sumber
8
+1 Ahh, aku tahu jawaban ini. Saya ditanyai ini dalam sebuah wawancara. Ini adalah pertanyaan yang sangat bagus untuk memahami bagaimana try / catch / akhirnya bekerja di stack.
Tapi Aku Bukan Pembungkus Kelas
10
Hanya ada satu pernyataan cetak yang dapat mencetak nomor (yang terakhir print(e.getMessage()):). Anda mengira hasilnya adalah AB2C3: apakah Anda pikir catchblok terluar akan dieksekusi dua kali?
Adrian Pronk
Di java, sebelum instruksi yang mentransfer kontrol keluar dari blok tangkap dijalankan, blok akhirnya dijalankan asalkan ada Jika hanya kode di blok akhirnya tidak mentransfer kontrol ke luar, instruksi tertunda dari blok menangkap dieksekusi.
Thomas

Jawaban:

198

Dari Spesifikasi Bahasa Jawa 14.20.2. :

Jika blok tangkap selesai tiba-tiba karena alasan R, maka blok akhirnya dijalankan. Lalu ada pilihan:

  • Jika blok akhirnya selesai secara normal, maka pernyataan coba selesai tiba-tiba karena alasan R.

  • Jika blok akhirnya selesai tiba-tiba karena alasan S, maka pernyataan coba selesai tiba-tiba karena alasan S (dan alasan R dibuang) .

Jadi, ketika ada blok penangkap yang melempar pengecualian:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

tetapi ada juga blok akhirnya yang juga melempar pengecualian:

} finally {
    throw new Exception("3");
}

Exception("2")akan dibuang dan hanya Exception("3")akan diperbanyak.

Adam Siemion
sumber
72
Ini bahkan berlaku untuk returnpernyataan. Jika blok Anda akhirnya memiliki pengembalian, itu akan menimpa pengembalian apa pun di blok tryatau catch. Karena "fitur" ini, praktik yang baik adalah bahwa pada akhirnya blok tidak boleh membuang pengecualian atau memiliki pernyataan pengembalian.
Augusto
Ini juga keunggulan inherit try-with-resources di Java 7. Ini mempertahankan pengecualian awal jika pengecualian sekunder dihasilkan saat menutup sumber daya, biasanya membuat proses debug lebih mudah.
w25r
19

Pengecualian yang dimasukkan akhirnya memblokir menekan pengecualian yang dilemparkan sebelumnya dalam mencoba atau menangkap blok.

Java 7 contoh: http://ideone.com/0YdeZo

Dari contoh Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Namun, dalam contoh ini, jika metode readLine dan menutup kedua melempar pengecualian, maka metode readFirstLineFromFileWithFinallyBlock melempar pengecualian yang dilemparkan dari blok akhirnya; pengecualian yang dilemparkan dari blok percobaan ditekan.


try-withSintaks baru Java 7 menambahkan langkah lain dari penindasan pengecualian: Pengecualian yang dilemparkan dalam blok coba menekan yang dilempar sebelumnya di bagian try-with.

dari contoh yang sama:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Pengecualian dapat dikeluarkan dari blok kode yang terkait dengan pernyataan coba-dengan-sumber daya. Dalam contoh di atas, pengecualian dapat dilempar dari blok percobaan, dan hingga dua pengecualian dapat dilemparkan dari pernyataan coba-dengan-sumber daya ketika mencoba untuk menutup objek ZipFile dan BufferedWriter. Jika pengecualian dilemparkan dari blok percobaan dan satu atau lebih pengecualian dilemparkan dari pernyataan coba-dengan-sumber daya, maka pengecualian-pengecualian yang dilemparkan dari pernyataan coba-dengan-sumber daya ditekan, dan pengecualian yang dilemparkan oleh blok adalah satu-satunya. yang dilemparkan oleh metode writeToFileZipFileContents. Anda dapat mengambil pengecualian yang ditekan ini dengan memanggil metode Throwable.getSuppressed dari pengecualian yang dilemparkan oleh blok try.


Dalam kode dari pertanyaan, setiap blok dengan jelas membuang pengecualian lama, bahkan tidak mencatatnya, tidak baik ketika Anda mencoba menyelesaikan beberapa bug:

http://en.wikipedia.org/wiki/Error_hiding

SD
sumber
9

Karena throw new Exception("2");terlempar dari catchblok dan tidak try, itu tidak akan ditangkap lagi.
Lihat 14.20.2. Eksekusi try-akhirnya dan try-catch-akhirnya .

Inilah yang terjadi:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}
Maroun
sumber
ya itu benar, saya melihat ini terjadi, tetapi saya sedang mencari penjelasan - mengapa berperilaku seperti ini
Kousalik
5

Pertanyaan Anda sangat jelas, dan jawabannya sederhana pada tingkat yang sama .. Objek Pengecualian dengan pesan "2" ditimpa oleh objek Pengecualian dengan pesan sebagai "3".

Penjelasan: Ketika Exception terjadi, objeknya dilemparkan untuk menangkap blok yang harus ditangani. Tetapi ketika pengecualian terjadi di catch block itu sendiri, objeknya ditransfer ke OUTER CATCH Block (jika ada) untuk Penanganan pengecualian. Dan hal yang sama terjadi di sini. Objek Pengecualian dengan pesan "2" ditransfer ke blok tangkapan OUTER. Tapi tunggu .. Sebelum meninggalkan blok try-catch bagian dalam, HARUS DIEKSEKUSI. Di sini terjadi perubahan yang kami khawatirkan. Objek PENGECUALIAN baru (dengan pesan "3") dibuang atau blok terakhir ini yang menggantikan objek Pengecualian yang sudah dibuang (dengan pesan "2"). Akibatnya, ketika pesan objek Pengecualian dicetak, kami mendapat nilai yang diganti yaitu "3" dan bukan "2".

Keep Remember: Hanya satu objek pengecualian yang dapat ditangani oleh pada blok CATCH.

Bharat
sumber
2

The finallyblok selalu berjalan. Baik Anda returndari dalam blok coba atau pengecualian dilemparkan. Pengecualian yang dilemparkan di finallyblok akan menimpa yang dilemparkan di cabang tangkapan.

Selain itu, melempar pengecualian tidak akan menyebabkan output apa pun dengan sendirinya. Baris throw new Exception("2");tidak akan menulis apa pun.

allprog
sumber
1
ya, saya tahu melempar output Exception tidak ada dengan sendirinya, tetapi saya tidak melihat alasan, mengapa Exception 2 harus dijatuhkan. Saya sedikit lebih pintar lagi :-)
Kousalik
selalu merupakan waktu yang sangat lama dan dalam waktu yang sangat lama segala sesuatu dapat terjadi (periksa puzzle wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

Menurut kode Anda:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Seperti yang Anda lihat di sini:

  1. cetak A dan berikan pengecualian # 1;
  2. pengecualian ini telah ditangkap oleh pernyataan penangkapan dan cetak B - # 2;
  3. blok akhirnya # 3dieksekusi setelah try-catch (atau hanya mencoba, jika tidak terjadi pengecualian) pernyataan dan cetakan C - # 4dan melemparkan pengecualian baru;
  4. yang ini ditangkap oleh pernyataan tangkapan eksternal # 5;

Hasilnya adalah ABC3. Dan 2dihilangkan dengan cara yang sama seperti1

nazar_art
sumber
Maaf, Pengecualian ("1") tidak dihilangkan, tetapi berhasil ditangkap
Black Maggie
@ Black Maggie Ini di-cache dan dilemparkan pengecualian baru => ini tidak di-cache dan program dihentikan. Dan sebelum blok ini akhirnya dieksekusi.
nazar_art