Pengecualian di Java, Bahagian 2: Ciri dan jenis lanjutan

JDK 1.0 memperkenalkan kerangka ciri bahasa dan jenis perpustakaan untuk menangani pengecualian , yang merupakan perbezaan dari tingkah laku program yang diharapkan. Separuh pertama tutorial ini merangkumi kemampuan pengendalian pengecualian asas Java. Separuh masa kedua ini memperkenalkan keupayaan yang lebih maju yang disediakan oleh JDK 1.0 dan penggantinya: JDK 1.4, JDK 7, dan JDK 9. Pelajari cara menjangkakan dan menguruskan pengecualian dalam program Java anda menggunakan ciri lanjutan seperti jejak tumpukan, sebab dan rantai pengecualian, cuba -dengan sumber, multi-tangkapan, lemparan semula akhir, dan tumpukan berjalan.

Perhatikan bahawa contoh kod dalam tutorial ini sesuai dengan JDK 12.

muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi dalam tutorial ini. Dicipta oleh Jeff Friesen untuk JavaWorld.

Pengendalian pengecualian dalam JDK 1.0 dan 1.4: Stack traces

Setiap JVM thread (a jalan pelaksanaan) dikaitkan dengan timbunan yang diwujudkan apabila benang dicipta. Struktur data ini terbahagi kepada bingkai , yang merupakan struktur data yang terkait dengan panggilan kaedah. Atas sebab ini, setiap timbunan benang sering disebut sebagai timbunan panggilan kaedah .

Kerangka baru dibuat setiap kali kaedah dipanggil. Setiap bingkai menyimpan pemboleh ubah tempatan, pemboleh ubah parameter (yang menahan argumen yang diteruskan ke metode), maklumat untuk kembali ke kaedah panggilan, ruang untuk menyimpan nilai kembali, maklumat yang berguna dalam pengiriman pengecualian, dan sebagainya.

A surih timbunan (juga dikenali sebagai jejak belakang timbunan ) adalah laporan bingkai tindanan aktif pada satu titik tertentu dalam masa semasa pelaksanaan thread ini. ThrowableKelas Java (dalam java.langpaket) menyediakan kaedah untuk mencetak jejak tumpukan, mengisi jejak tumpukan, dan mengakses elemen jejak tumpukan.

Mencetak jejak timbunan

Apabila throwpernyataan melempar dilempar, pertama mencari catchblok yang sesuai dalam kaedah pelaksanaan. Sekiranya tidak dijumpai, ia melepaskan timbunan panggilan kaedah untuk mencari catchblok terdekat yang dapat menangani pengecualian. Sekiranya tidak dijumpai, JVM ditamatkan dengan mesej yang sesuai. Pertimbangkan Penyenaraian 1.

Penyenaraian 1. PrintStackTraceDemo.java(versi 1)

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

Contoh penyenaraian 1 membuat java.io.IOExceptionobjek dan membuang objek ini daripada main()kaedah. Kerana main()tidak dapat menangani halangan ini, dan kerana main()merupakan kaedah tingkat atas, JVM diakhiri dengan mesej yang sesuai. Untuk aplikasi ini, anda akan melihat mesej berikut:

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

JVM output mesej ini dengan memanggil Throwable's void printStackTrace()kaedah, yang mencetak jejak timbunan untuk menyeru Throwableobjek pada aliran ralat piawai. Baris pertama menunjukkan hasil penggunaan toString()kaedah lemparan . Baris seterusnya menunjukkan data yang sebelumnya direkodkan oleh fillInStackTrace()(dibincangkan tidak lama lagi).

Kaedah jejak cetakan tambahan

ThrowableOverload void printStackTrace(PrintStream ps)dan void printStackTrace(PrintWriter pw)kaedah mengeluarkan jejak tumpukan ke aliran atau penulis yang ditentukan.

Jejak timbunan mendedahkan fail sumber dan nombor baris tempat dilempar dibuat. Dalam kes ini, ia dibuat pada Baris 7 PrintStackTrace.javafail sumber.

Anda boleh meminta printStackTrace()secara langsung, biasanya dari catchblok. Sebagai contoh, pertimbangkan versi kedua PrintStackTraceDemoaplikasi.

Penyenaraian 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(); } }

Penyenaraian 2 menunjukkan main()kaedah yang memanggil kaedah a(), yang memanggil kaedah b(). Cara b()melemparkan satu IOExceptionobjek kepada JVM, yang unwinds timbunan kaedah-call sehingga ia mendapati main()'s catchblok, yang boleh mengendalikan pengecualian. Pengecualian tersebut ditangani dengan menggunakan printStackTrace()alat pelempar. Kaedah ini menghasilkan output berikut:

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

printStackTrace()tidak mengeluarkan nama utas. Sebagai gantinya, ia toString()meminta pelempar untuk mengembalikan nama kelas yang layak sepenuhnya ( java.io.IOException), yang dikeluarkan pada baris pertama. Kemudian mengeluarkan hierarki kaedah-panggilan: kaedah yang paling baru dipanggil ( b()) berada di bahagian atas dan main()berada di bahagian bawah.

Garis apa yang dikenal pasti jejak timbunan?

Jejak timbunan mengenal pasti garis di mana dilempar dibuat. Ia tidak mengenal pasti garis di mana dilempar dilemparkan (melalui throw), melainkan dilempar dilemparkan pada garis yang sama di mana ia dilemparkan.

Mengisi jejak timbunan

Throwablemenyatakan Throwable fillInStackTrace()kaedah yang mengisi jejak pelaksanaan. Dalam Throwableobjek pemanggil , ia mencatat maklumat mengenai keadaan bingkai susun benang semasa. Pertimbangkan Penyenaraian 3.

Penyenaraian 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(); } }

Perbezaan utama antara Penyenaraian 3 dan Penyenaraian 2 adalah penyataan catchblok throw (IOException) ioe.fillInStackTrace();. Pernyataan ini menggantikan ioejejak timbunan, selepas itu lemparan dilemparkan semula. Anda harus melihat output 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)

Daripada mengulangi jejak tumpukan awal, yang mengidentifikasi lokasi di mana IOExceptionobjek itu dibuat, jejak tumpukan kedua menunjukkan lokasi ioe.fillInStackTrace().

Pembina lempar dan fillInStackTrace()

Setiap Throwablepembina memanggil fillInStackTrace(). Walau bagaimanapun, pembina berikut (diperkenalkan pada JDK 7) tidak akan sembah kaedah ini apabila anda lulus falseuntuk writableStackTrace:

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

fillInStackTrace()menggunakan kaedah semula jadi yang menggunakan susunan panggilan kaedah benang semasa untuk membina jejak timbunan. Jalan ini mahal dan boleh memberi kesan kepada prestasi sekiranya berlaku terlalu kerap.

Sekiranya anda menghadapi situasi (mungkin melibatkan peranti tertanam) di mana prestasi sangat kritikal, anda boleh mengelakkan jejak timbunan dibina dengan menimpa fillInStackTrace(). Lihat Penyenaraian 4.

Penyenaraian 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()kaedah untuk Throwable. Kaedah ini dirancang untuk digunakan oleh kerangka panggilan prosedur jauh (RPC) dan sistem canggih lain, yang memungkinkan pelanggan untuk mengatasi jejak timbunan lalai yang dihasilkan oleh fillInStackTrace()ketika dilempar dibina.

Saya sebelum ini menunjukkan cara mengganti fillInStackTrace()untuk mengelakkan jejak timbunan daripada dibina. Sebagai gantinya, anda boleh memasang jejak timbunan baru dengan menggunakan StackTraceElementdan setStackTrace(). Buat array StackTraceElementobjek yang diinisialisasi melalui konstruktor berikut, dan lulus array ini ke setStackTrace():

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

Penyenaraian 6 menunjukkan StackTraceElementdan setStackTrace().