Pengecualian di Java, Bagian 2: Fitur dan tipe lanjutan

JDK 1.0 memperkenalkan kerangka kerja fitur bahasa dan tipe pustaka untuk menangani pengecualian , yang merupakan perbedaan dari perilaku program yang diharapkan. Paruh pertama dari tutorial ini membahas kemampuan penanganan pengecualian dasar Java. Paruh kedua ini memperkenalkan kemampuan yang lebih canggih yang disediakan oleh JDK 1.0 dan penerusnya: JDK 1.4, JDK 7, dan JDK 9. Pelajari cara mengantisipasi dan mengelola pengecualian dalam program Java Anda menggunakan fitur-fitur canggih seperti pelacakan tumpukan, rangkaian penyebab dan pengecualian, coba -with-resources, multi-catch, final re-throw, dan stack walking.

Perhatikan bahwa contoh kode dalam tutorial ini kompatibel dengan JDK 12.

unduh Dapatkan kodenya Unduh kode sumber untuk aplikasi contoh dalam tutorial ini. Dibuat oleh Jeff Friesen untuk JavaWorld.

Penanganan pengecualian di JDK 1.0 dan 1.4: Pelacakan tumpukan

Setiap utas JVM (jalur eksekusi) dikaitkan dengan tumpukan yang dibuat saat utas dibuat. Struktur data ini dibagi menjadi beberapa frame , yaitu struktur data yang terkait dengan pemanggilan metode. Karena alasan ini, setiap tumpukan thread sering disebut sebagai tumpukan panggilan metode .

Bingkai baru dibuat setiap kali metode dipanggil. Setiap frame menyimpan variabel lokal, variabel parameter (yang menampung argumen yang diteruskan ke metode), informasi untuk kembali ke metode panggilan, ruang untuk menyimpan nilai yang dikembalikan, informasi yang berguna dalam mengirimkan pengecualian, dan sebagainya.

Sebuah jejak stack (juga dikenal sebagai backtrace tumpukan ) adalah laporan dari frame tumpukan aktif pada titik tertentu dalam waktu selama eksekusi thread ini. ThrowableKelas Java (dalam java.langpaket) menyediakan metode untuk mencetak pelacakan tumpukan, mengisi pelacakan tumpukan, dan mengakses elemen pelacakan tumpukan.

Mencetak jejak tumpukan

Ketika throwpernyataan melempar sebuah lemparan, pertama-tama pernyataan itu mencari catchblok yang sesuai dalam metode eksekusi. Jika tidak ditemukan, ia melepaskan tumpukan panggilan metode mencari catchblok terdekat yang bisa menangani pengecualian. Jika tidak ditemukan, JVM diakhiri dengan pesan yang sesuai. Pertimbangkan Daftar 1.

Daftar 1. PrintStackTraceDemo.java(versi 1)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { throw new IOException(); } }

Contoh buatan Listing 1 membuat sebuah java.io.IOExceptionobjek dan membuang objek ini keluar dari main()metode. Karena main()tidak menangani pelontaran ini, dan karena main()merupakan metode tingkat atas, JVM diakhiri dengan pesan yang sesuai. Untuk aplikasi ini, Anda akan melihat pesan berikut:

Exception in thread "main" java.io.IOException at PrintStackTraceDemo.main(PrintStackTraceDemo.java:7)

JVM output pesan ini dengan memanggil Throwable's void printStackTrace()metode, yang mencetak setumpuk jejak untuk memohon Throwableobjek pada aliran standard error. Baris pertama menunjukkan hasil pemanggilan metode throwable toString(). Baris berikutnya menunjukkan data yang sebelumnya direkam oleh fillInStackTrace()(dibahas segera).

Metode jejak tumpukan cetak tambahan

Throwablekelebihan beban void printStackTrace(PrintStream ps)dan void printStackTrace(PrintWriter pw)metode mengeluarkan pelacakan tumpukan ke aliran atau penulis yang ditentukan.

Pelacakan tumpukan mengungkapkan file sumber dan nomor baris tempat throwable dibuat. Dalam hal ini, itu dibuat di Baris 7 dari PrintStackTrace.javafile sumber.

Anda dapat memanggil printStackTrace()secara langsung, biasanya dari satu catchblok. Misalnya, pertimbangkan versi kedua dari PrintStackTraceDemoaplikasi tersebut.

Daftar 2. PrintStackTraceDemo.java(versi 2)

import java.io.IOException; public class PrintStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

Kode 2 mengungkapkan main()metode yang memanggil metode a(), yang memanggil metode b(). Metode b()melempar IOExceptionbenda ke JVM, yang unwinds tumpukan metode-panggilan sampai menemukan main()'s catchblok, yang dapat menangani pengecualian. Pengecualian ditangani dengan memanggil printStackTrace()throwable. Metode ini menghasilkan keluaran berikut:

java.io.IOException at PrintStackTraceDemo.b(PrintStackTraceDemo.java:24) at PrintStackTraceDemo.a(PrintStackTraceDemo.java:19) at PrintStackTraceDemo.main(PrintStackTraceDemo.java:9)

printStackTrace()tidak menampilkan nama utas. Sebaliknya, ini memanggil toString()pada throwable untuk mengembalikan nama kelas yang sepenuhnya memenuhi syarat ( java.io.IOException), yang merupakan keluaran pada baris pertama. Ini kemudian mengeluarkan hierarki panggilan metode: metode yang paling baru dipanggil method ( b()) ada di atas dan main()di bawah.

Baris apa yang diidentifikasi oleh pelacakan tumpukan?

Pelacakan tumpukan mengidentifikasi garis tempat pembuatan lemparan. Itu tidak mengidentifikasi garis tempat lemparan dilemparkan (melalui throw), kecuali lemparan dilemparkan pada garis yang sama tempat lemparan dibuat.

Mengisi jejak tumpukan

Throwablemendeklarasikan Throwable fillInStackTrace()metode yang mengisi pelacakan tumpukan eksekusi. Dalam Throwableobjek pemanggilan , ia mencatat informasi tentang keadaan saat ini dari bingkai tumpukan thread saat ini. Pertimbangkan Listing 3.

Kode 3. FillInStackTraceDemo.java(versi 1)

import java.io.IOException; public class FillInStackTraceDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { ioe.printStackTrace(); System.out.println(); throw (IOException) ioe.fillInStackTrace(); } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

Perbedaan utama antara Listing 3 dan Listing 2 adalah pernyataan catchblok itu throw (IOException) ioe.fillInStackTrace();. Pernyataan ini menggantikan ioepelacakan tumpukan, setelah itu lemparan kembali dilemparkan. Anda harus mengamati keluaran ini:

java.io.IOException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:26) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:21) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:9) Exception in thread "main" java.io.IOException at FillInStackTraceDemo.main(FillInStackTraceDemo.java:15)

Alih-alih mengulangi pelacakan tumpukan awal, yang mengidentifikasi lokasi tempat IOExceptionobjek dibuat, pelacakan tumpukan kedua mengungkapkan lokasi ioe.fillInStackTrace().

Konstruktor yang dapat dibuang dan fillInStackTrace()

Masing-masing Throwablekonstruktor memanggil fillInStackTrace(). Namun, konstruktor berikut (diperkenalkan di JDK 7) tidak akan memanggil metode ini saat Anda meneruskan falseke writableStackTrace:

Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)

fillInStackTrace()memanggil metode native yang berjalan di tumpukan panggilan metode thread saat ini untuk membuat pelacakan tumpukan. Jalan kaki ini mahal dan dapat memengaruhi kinerja jika terlalu sering dilakukan.

Jika Anda mengalami situasi (mungkin melibatkan perangkat yang disematkan) di mana kinerja sangat penting, Anda dapat mencegah pelacakan tumpukan agar tidak dibuat dengan mengganti fillInStackTrace(). Lihat Daftar 4.

Daftar 4. FillInStackTraceDemo.java(versi 2)

{ public static void main(String[] args) throws NoStackTraceException { try { a(); } catch (NoStackTraceException nste) { nste.printStackTrace(); } } static void a() throws NoStackTraceException { b(); } static void b() throws NoStackTraceException { throw new NoStackTraceException(); } } class NoStackTraceException extends Exception { @Override public synchronized Throwable fillInStackTrace() { return this; } }

Listing 4 introduces NoStackTraceException. This custom checked exception class overrides fillInStackTrace() to return this -- a reference to the invoking Throwable. This program generates the following output:

NoStackTraceException

Comment out the overriding fillInStackTrace() method and you'll observe the following output:

NoStackTraceException at FillInStackTraceDemo.b(FillInStackTraceDemo.java:22) at FillInStackTraceDemo.a(FillInStackTraceDemo.java:17) at FillInStackTraceDemo.main(FillInStackTraceDemo.java:7)

Accessing a stack trace's elements

At times you'll need to access a stack trace's elements in order to extract details required for logging, identifying the source of a resource leak, and other purposes. The printStackTrace() and fillInStackTrace() methods don't support this task, but JDK 1.4 introduced java.lang.StackTraceElement and its methods for this purpose.

The java.lang.StackTraceElement class describes an element representing a stack frame in a stack trace. Its methods can be used to return the fully-qualified name of the class containing the execution point represented by this stack trace element along with other useful information. Here are the main methods:

  • String getClassName() returns the fully-qualified name of the class containing the execution point represented by this stack trace element.
  • String getFileName() returns the name of the source file containing the execution point represented by this stack trace element.
  • int getLineNumber() returns the line number of the source line containing the execution point represented by this stack trace element.
  • String getMethodName() returns the name of the method containing the execution point represented by this stack trace element.
  • boolean isNativeMethod() returns true when the method containing the execution point represented by this stack trace element is a native method.

JDK 1.4 also introduced the StackTraceElement[] getStackTrace() method to the java.lang.Thread and Throwable classes. This method respectively returns an array of stack trace elements representing the invoking thread's stack dump and provides programmatic access to the stack trace information printed by printStackTrace().

Listing 5 demonstrates StackTraceElement and getStackTrace().

Listing 5. StackTraceElementDemo.java (version 1)

import java.io.IOException; public class StackTraceElementDemo { public static void main(String[] args) throws IOException { try { a(); } catch (IOException ioe) { StackTraceElement[] stackTrace = ioe.getStackTrace(); for (int i = 0; i < stackTrace.length; i++) { System.err.println("Exception thrown from " + stackTrace[i].getMethodName() + " in class " + stackTrace[i].getClassName() + " on line " + stackTrace[i].getLineNumber() + " of file " + stackTrace[i].getFileName()); System.err.println(); } } } static void a() throws IOException { b(); } static void b() throws IOException { throw new IOException(); } }

When you run this application, you'll observe the following output:

Exception thrown from b in class StackTraceElementDemo on line 33 of file StackTraceElementDemo.java Exception thrown from a in class StackTraceElementDemo on line 28 of file StackTraceElementDemo.java Exception thrown from main in class StackTraceElementDemo on line 9 of file StackTraceElementDemo.java

Akhirnya, JDK 1.4 memperkenalkan setStackTrace()metode tersebut ke Throwable. Metode ini dirancang untuk digunakan oleh kerangka kerja remote procedure call (RPC) dan sistem lanjutan lainnya, yang memungkinkan klien untuk mengganti pelacakan tumpukan default yang dihasilkan fillInStackTrace()saat throwable dibuat.

Saya sebelumnya menunjukkan cara mengganti fillInStackTrace()untuk mencegah pelacakan tumpukan agar tidak dibuat. Sebagai gantinya, Anda dapat menginstal pelacakan tumpukan baru dengan menggunakan StackTraceElementdan setStackTrace(). Buat larik StackTraceElementobjek yang diinisialisasi melalui konstruktor berikut, dan teruskan larik ini ke setStackTrace():

StackTraceElement(String declaringClass, String methodName, String fileName, int lineNumber)

Kode 6 menunjukkan StackTraceElementdan setStackTrace().