Изключения в Java, част 2: Разширени функции и типове

JDK 1.0 въведе рамка от езикови функции и типове библиотеки за справяне с изключения , които са отклонения от очакваното поведение на програмата. Първата половина на този урок обхваща основните възможности на Java за обработка на изключения. Тази втора половина представя по-разширени възможности, предоставени от JDK 1.0 и неговите наследници: JDK 1.4, JDK 7 и JDK 9. Научете как да предвиждате и управлявате изключения във вашите Java програми, използвайки разширени функции като следи от стекове, причини и вериги на изключения, опитайте -with-resources, multi-catch, final re-throw и stack ходене.

Имайте предвид, че примерите за кодове в този урок са съвместими с JDK 12.

изтегляне Вземете кода Изтеглете изходния код, например приложения в този урок. Създадено от Jeff Friesen за JavaWorld.

Обработка на изключения в JDK 1.0 и 1.4: Трасиране на стека

Всяка JVM нишка (път на изпълнение) е свързана със стек , създаден при създаването на нишката. Тази структура от данни е разделена на кадри , които представляват структури от данни, свързани с извиквания на методи. Поради тази причина стекът на всяка нишка често се нарича стек за извикване на метод .

Създава се нов кадър при всяко извикване на метод. Всеки кадър съхранява локални променливи, променливи на параметри (които съдържат аргументи, предадени на метода), информация за връщане към извикващия метод, пространство за съхраняване на възвръщаема стойност, информация, която е полезна при изпращане на изключение и т.н.

А на стека (известен също като комин обратно проследяване ) е доклад на активните стак форми в определен момент от време по време на изпълнението на нишка. ThrowableКласът на Java (в java.langпакета) предоставя методи за отпечатване на проследяване на стека, попълване на проследяване на стека и достъп до елементите на проследяване на стека.

Отпечатване на стека на стека

Когато throwоператорът изхвърля хвърлимост, той първо търси подходящ catchблок в изпълняващия метод. Ако не бъде намерен, той размотава стека за извикване на метод, търсейки най-близкия catchблок, който може да се справи с изключението. Ако не бъде намерен, JVM завършва с подходящо съобщение. Помислете за списък 1.

Листинг 1. PrintStackTraceDemo.java(версия 1)

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

Измисленият пример от Листинг 1 създава java.io.IOExceptionобект и изхвърля този обект от main()метода. Тъй като main()не се справя с тази възможност за хвърляне и тъй като main()е методът от най-високо ниво, JVM завършва с подходящо съобщение. За това приложение ще видите следното съобщение:

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

JVM извежда това съобщение чрез извикване Throwableна void printStackTrace()метод, който отпечатва проследяване на стека за извикващия Throwableобект на стандартния поток на грешка. Първият ред показва резултата от извикването на toString()метода на хвърляния . Следващият ред показва данни, записани преди това fillInStackTrace()(обсъдени скоро).

Допълнителни методи за проследяване на стека на печат

Throwableе претоварен void printStackTrace(PrintStream ps)и void printStackTrace(PrintWriter pw)методите извеждат трасирането на стека към посочения поток или запис.

Проследяването на стека разкрива изходния файл и номера на реда, където е създадено изхвърляното. В този случай той е създаден на ред 7 на PrintStackTrace.javaизходния файл.

Можете да извикате printStackTrace()директно, обикновено от catchблок. Например, помислете за втора версия на PrintStackTraceDemoприложението.

Листинг 2. PrintStackTraceDemo.java(версия 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(); } }

Листинг 2 разкрива main()метод, който извиква метод a(), който извиква метод b(). Метод b()хвърля IOExceptionобект на JVM, която се развива стека на метод на разговор, докато намери main()е catchблок, който може да се справи изключение. Изключението се обработва чрез извикване printStackTrace()на хвърлимия. Този метод генерира следния изход:

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

printStackTrace()не извежда името на нишката. Вместо това той извиква toString()хвърлимото, за да върне напълно квалифицираното име на класа ( java.io.IOException), което се извежда на първия ред. След това извежда йерархията на метода-извикване: най-наскоро извиканият метод ( b()) е отгоре и main()отдолу.

Коя линия идентифицира проследяването на стека?

Проследяването на стека идентифицира линията, където е създадена възможност за изхвърляне. Той не идентифицира линията, където хвърляното е хвърлено (чрез throw), освен ако хвърляното не е хвърлено на същата линия, където е създадено.

Попълване на проследяване на стека

Throwableдекларира Throwable fillInStackTrace()метод, който запълва проследяването на стека за изпълнение. В Throwableобекта за извикване той записва информация за текущото състояние на рамките на стека на текущата нишка. Помислете за списък 3.

Листинг 3. FillInStackTraceDemo.java(версия 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(); } }

Основната разлика между Листинг 3 и Листинг 2 е изявлението на catchблока throw (IOException) ioe.fillInStackTrace();. Това изявление замества ioeпроследяването на стека, след което изхвърляното се прехвърля отново. Трябва да наблюдавате този изход:

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)

Вместо да повтаря първоначалната проследяване на стека, която идентифицира местоположението, където IOExceptionе създаден обектът, втората проследяване на стека разкрива местоположението на ioe.fillInStackTrace().

Хвърлящи се конструктори и fillInStackTrace()

Всеки от Throwable'конструкторите извиква fillInStackTrace(). Следващият конструктор (въведен в JDK 7) няма да извика този метод, когато преминете falseкъм writableStackTrace:

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

fillInStackTrace()извиква собствен метод, който преминава по стека на метода за извикване на текущата нишка, за да изгради трасирането на стека. Тази разходка е скъпа и може да повлияе на производителността, ако се случва твърде често.

Ако се сблъскате със ситуация (може би с вградено устройство), където производителността е критична, можете да предотвратите изграждането на стека на стека, като замените fillInStackTrace(). Вижте списък 4.

Листинг 4. FillInStackTraceDemo.java(версия 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

И накрая, JDK 1.4 представи setStackTrace()метода на Throwable. Този метод е предназначен за използване от рамки за отдалечено извикване на процедури (RPC) и други усъвършенствани системи, позволяващи на клиента да замени проследяването на стека по подразбиране, което се генерира fillInStackTrace()при конструиране на хвърлящ елемент

Преди това показах как да се замени, за fillInStackTrace()да се предотврати изграждането на следа от стека. Вместо това можете да инсталирате нова проследяване на стека, като използвате StackTraceElementи setStackTrace(). Създайте масив от StackTraceElementобекти, инициализирани чрез следния конструктор, и предайте този масив на setStackTrace():

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

Листинг 6 демонстрира StackTraceElementи setStackTrace().