ข้อยกเว้นใน Java ตอนที่ 1: พื้นฐานการจัดการข้อยกเว้น

ข้อยกเว้นของ Java คือประเภทไลบรารีและคุณลักษณะของภาษาที่ใช้เพื่อแสดงและจัดการกับความล้มเหลวของโปรแกรม หากคุณต้องการทำความเข้าใจว่าความล้มเหลวแสดงในซอร์สโค้ดอย่างไรคุณมาถูกที่แล้ว นอกเหนือจากภาพรวมของข้อยกเว้น Java ฉันจะช่วยให้คุณเริ่มต้นด้วยคุณสมบัติภาษาของ Java สำหรับการขว้างปาสิ่งของลองใช้รหัสที่อาจล้มเหลวจับวัตถุที่ถูกโยนและล้างโค้ด Java ของคุณหลังจากที่มีข้อยกเว้นเกิดขึ้น

ในช่วงครึ่งแรกของบทช่วยสอนนี้คุณจะได้เรียนรู้เกี่ยวกับคุณสมบัติภาษาพื้นฐานและประเภทไลบรารีที่มีมาตั้งแต่ Java 1.0 ในช่วงครึ่งหลังคุณจะพบกับความสามารถขั้นสูงที่นำมาใช้ใน Java เวอร์ชันล่าสุด

โปรดทราบว่าตัวอย่างโค้ดในบทช่วยสอนนี้เข้ากันได้กับ JDK 12

ดาวน์โหลดรับโค้ดดาวน์โหลดซอร์สโค้ดสำหรับแอปพลิเคชันตัวอย่างในบทช่วยสอนนี้ สร้างโดย Jeff Friesen สำหรับ JavaWorld

ข้อยกเว้นของ Java คืออะไร?

ความล้มเหลวเกิดขึ้นเมื่อพฤติกรรมปกติของโปรแกรม Java ถูกขัดจังหวะด้วยพฤติกรรมที่ไม่คาดคิด ความแตกต่างนี้เป็นที่รู้จักในฐานะที่เป็นข้อยกเว้น ตัวอย่างเช่นโปรแกรมพยายามเปิดไฟล์เพื่ออ่านเนื้อหา แต่ไม่มีไฟล์อยู่ Java แบ่งประเภทข้อยกเว้นออกเป็นสองสามประเภทดังนั้นลองพิจารณาแต่ละประเภท

ตรวจสอบข้อยกเว้น

ข้อยกเว้น Java จัดประเภทที่เกิดจากปัจจัยภายนอก (เช่นไฟล์ที่หายไป) เป็นข้อยกเว้นการตรวจสอบ คอมไพเลอร์ Java ตรวจสอบว่าข้อยกเว้นดังกล่าวได้รับการจัดการ (แก้ไข) เมื่อเกิดขึ้นหรือจัดทำเป็นเอกสารเพื่อจัดการที่อื่น

ตัวจัดการข้อยกเว้น

จัดการข้อยกเว้นเป็นลำดับของรหัสที่จัดการข้อยกเว้น มันซักถามบริบท - หมายความว่าอ่านค่าที่บันทึกไว้จากตัวแปรที่อยู่ในขอบเขต ณ เวลาที่เกิดข้อยกเว้น - จากนั้นใช้สิ่งที่เรียนรู้เพื่อคืนค่าโปรแกรม Java ให้เป็นโฟลว์ของพฤติกรรมปกติ ตัวอย่างเช่นตัวจัดการข้อยกเว้นอาจอ่านชื่อไฟล์ที่บันทึกไว้และแจ้งให้ผู้ใช้แทนที่ไฟล์ที่หายไป

ข้อยกเว้นรันไทม์ (ไม่เลือก)

สมมติว่าความพยายามโปรแกรมจะแบ่งเป็นจำนวนเต็มโดยจำนวนเต็ม 0. เป็นไปไม่ได้นี้แสดงให้เห็นชนิดของข้อยกเว้นอีกกล่าวคือข้อยกเว้นรันไทม์ ซึ่งแตกต่างจากข้อยกเว้นที่ตรวจสอบแล้วข้อยกเว้นรันไทม์มักเกิดจากซอร์สโค้ดที่เขียนไม่ดีดังนั้นโปรแกรมเมอร์จึงควรแก้ไข เพราะคอมไพเลอร์ไม่ได้ตรวจสอบว่าข้อยกเว้นรันไทม์ได้รับการจัดการหรือเอกสารที่จะจัดการอื่น ๆ ที่คุณสามารถคิดข้อยกเว้นรันไทม์เป็นข้อยกเว้นที่ไม่ถูกตรวจสอบ

เกี่ยวกับข้อยกเว้นรันไทม์

คุณอาจแก้ไขโปรแกรมเพื่อจัดการกับข้อยกเว้นรันไทม์ แต่ควรแก้ไขซอร์สโค้ดจะดีกว่า ข้อยกเว้นรันไทม์มักเกิดจากการส่งอาร์กิวเมนต์ที่ไม่ถูกต้องไปยังเมธอดของไลบรารี ควรแก้ไขรหัสการโทรแบบบั๊กกี้

ข้อผิดพลาด

ข้อยกเว้นบางประการมีความร้ายแรงมากเนื่องจากเป็นอันตรายต่อความสามารถของโปรแกรมในการดำเนินการต่อไป ตัวอย่างเช่นโปรแกรมพยายามจัดสรรหน่วยความจำจาก JVM แต่มีหน่วยความจำว่างไม่เพียงพอที่จะตอบสนองคำขอ สถานการณ์ร้ายแรงอีกประการหนึ่งเกิดขึ้นเมื่อโปรแกรมพยายามโหลด classfile ผ่านClass.forName()การเรียกใช้ method แต่ classfile เสียหาย ชนิดของข้อยกเว้นนี้เป็นที่รู้จักกันในฐานะที่เป็นข้อผิดพลาด คุณไม่ควรพยายามจัดการกับข้อผิดพลาดด้วยตนเองเนื่องจาก JVM อาจไม่สามารถกู้คืนได้

ข้อยกเว้นในซอร์สโค้ด

ข้อยกเว้นอาจจะแสดงในรหัสที่มาเป็นรหัสข้อผิดพลาดหรือเป็นวัตถุ ฉันจะแนะนำทั้งสองอย่างและแสดงให้คุณเห็นว่าทำไมวัตถุถึงเหนือกว่า

รหัสข้อผิดพลาดกับวัตถุ

ภาษาการเขียนโปรแกรมเช่น C ใช้รหัสข้อผิดพลาดที่อิงจำนวนเต็มเพื่อแสดงถึงความล้มเหลวและสาเหตุของความล้มเหลวนั่นคือข้อยกเว้น นี่คือตัวอย่างสองสามตัวอย่าง:

if (chdir("C:\\temp")) printf("Unable to change to temp directory: %d\n", errno); FILE *fp = fopen("C:\\temp\\foo"); if (fp == NULL) printf("Unable to open foo: %d\n", errno);

chdir()ฟังก์ชันC (เปลี่ยนไดเร็กทอรี) ส่งคืนจำนวนเต็ม: 0 เมื่อสำเร็จหรือ -1 เมื่อล้มเหลว ในทำนองเดียวกันfopen()ฟังก์ชันC (เปิดไฟล์) จะส่งกลับตัวชี้ที่ไม่เป็นตัวเลข (ที่อยู่จำนวนเต็ม) ไปยังFILEโครงสร้างของความสำเร็จหรือตัวชี้ null (0) (แสดงโดยค่าคงที่NULL) เมื่อเกิดความล้มเหลว ในทั้งสองกรณีเพื่อระบุข้อยกเว้นที่ทำให้เกิดความล้มเหลวคุณต้องอ่านerrnoรหัสข้อผิดพลาดที่อิงจำนวนเต็มของตัวแปรส่วนกลาง

รหัสข้อผิดพลาดทำให้เกิดปัญหา:

  • จำนวนเต็มไม่มีความหมาย พวกเขาไม่ได้อธิบายถึงข้อยกเว้นที่เป็นตัวแทน ตัวอย่างเช่น 6 หมายถึงอะไร?
  • การเชื่อมโยงบริบทกับรหัสข้อผิดพลาดเป็นเรื่องที่ไม่สะดวก ตัวอย่างเช่นคุณอาจต้องการแสดงชื่อไฟล์ที่ไม่สามารถเปิดได้ แต่คุณจะเก็บชื่อไฟล์ไว้ที่ไหน?
  • จำนวนเต็มเป็นจำนวนเต็มซึ่งอาจทำให้เกิดความสับสนเมื่ออ่านซอร์สโค้ด ตัวอย่างเช่นการระบุif (!chdir("C:\\temp"))( !หมายถึง NOT) แทนที่จะif (chdir("C:\\temp"))ทดสอบความล้มเหลวนั้นชัดเจนกว่า อย่างไรก็ตาม 0 ถูกเลือกเพื่อบ่งชี้ความสำเร็จดังนั้นจึงif (chdir("C:\\temp"))ต้องระบุเพื่อทดสอบความล้มเหลว
  • รหัสข้อผิดพลาดนั้นง่ายเกินไปที่จะเพิกเฉยซึ่งอาจนำไปสู่รหัสข้อผิดพลาด ตัวอย่างเช่นโปรแกรมเมอร์สามารถระบุchdir("C:\\temp");และละเว้นการif (fp == NULL)ตรวจสอบได้ errnoนอกจากนี้จำเป็นที่จะต้องเขียนโปรแกรมได้ตรวจสอบ โดยการไม่ทดสอบความล้มเหลวโปรแกรมจะทำงานผิดปกติเมื่อฟังก์ชันใดฟังก์ชันหนึ่งส่งกลับตัวบ่งชี้ความล้มเหลว

เพื่อแก้ปัญหาเหล่านี้ Java ได้ใช้แนวทางใหม่ในการจัดการข้อยกเว้น ใน Java เรารวมวัตถุที่อธิบายข้อยกเว้นเข้ากับกลไกโดยอาศัยการขว้างปาและจับวัตถุเหล่านี้ นี่คือข้อดีบางประการของการใช้อ็อบเจกต์เทียบกับรหัสข้อผิดพลาดเพื่อแสดงถึงข้อยกเว้น:

  • ออบเจ็กต์สามารถสร้างขึ้นจากคลาสที่มีชื่อที่มีความหมาย ตัวอย่างเช่นFileNotFoundException(ในjava.ioแพ็คเกจ) มีความหมายมากกว่า 6
  • ออบเจ็กต์สามารถจัดเก็บบริบทในช่องต่างๆ ตัวอย่างเช่นคุณสามารถจัดเก็บข้อความชื่อไฟล์ที่ไม่สามารถเปิดได้ตำแหน่งล่าสุดที่การดำเนินการแยกวิเคราะห์ล้มเหลวและ / หรือรายการอื่น ๆ ในฟิลด์ของออบเจ็กต์
  • คุณไม่ได้ใช้ifงบเพื่อทดสอบความล้มเหลว แต่วัตถุข้อยกเว้นจะถูกโยนไปยังตัวจัดการที่แยกจากรหัสโปรแกรม ด้วยเหตุนี้ซอร์สโค้ดจึงอ่านได้ง่ายขึ้นและมีโอกาสน้อยที่จะเกิดข้อผิดพลาด

Throwable และคลาสย่อย

Java จัดเตรียมลำดับชั้นของคลาสที่แสดงถึงข้อยกเว้นประเภทต่างๆ ชั้นเรียนเหล่านี้ถูกฝังรากลึกอยู่ในjava.langแพคเกจThrowableระดับพร้อมกับมันException, RuntimeExceptionและErrorsubclasses

Throwableเป็นซูเปอร์คลาสที่ดีที่สุดที่เกี่ยวข้องกับข้อยกเว้น เฉพาะออบเจ็กต์ที่สร้างจากThrowableและคลาสย่อยเท่านั้นที่สามารถโยนได้ (และถูกจับในภายหลัง) วัตถุดังกล่าวเป็นที่รู้จักกันthrowables

A Throwable object is associated with a detail message that describes an exception. Several constructors, including the pair described below, are provided to create a Throwable object with or without a detail message:

  • Throwable() creates a Throwable with no detail message. This constructor is appropriate for situations where there is no context. For example, you only want to know that a stack is empty or full.
  • Throwable(String message) creates a Throwable with message as the detail message. This message can be output to the user and/or logged.

Throwable provides the String getMessage() method to return the detail message. It also provides additional useful methods, which I'll introduce later.

The Exception class

Throwable has two direct subclasses. One of these subclasses is Exception, which describes an exception arising from an external factor (such as attempting to read from a nonexistent file). Exception declares the same constructors (with identical parameter lists) as Throwable, and each constructor invokes its Throwable counterpart. Exception inherits Throwable's methods; it declares no new methods.

Java provides many exception classes that directly subclass Exception. Here are three examples:

  • CloneNotSupportedException signals an attempt to clone an object whose class doesn't implement the Cloneable interface. Both types are in the java.lang package.
  • IOException signals that some kind of I/O failure has occurred. This type is located in the java.io package.
  • ParseException signals that a failure has occurred while parsing text. This type can be found in the java.text package.

Notice that each Exception subclass name ends with the word Exception. This convention makes it easy to identify the class's purpose.

You'll typically subclass Exception (or one of its subclasses) with your own exception classes (whose names should end with Exception). Here are a couple of custom subclass examples:

public class StackFullException extends Exception { } public class EmptyDirectoryException extends Exception { private String directoryName; public EmptyDirectoryException(String message, String directoryName) { super(message); this.directoryName = directoryName; } public String getDirectoryName() { return directoryName; } }

The first example describes an exception class that doesn't require a detail message. It's default noargument constructor invokes Exception(), which invokes Throwable().

The second example describes an exception class whose constructor requires a detail message and the name of the empty directory. The constructor invokes Exception(String message), which invokes Throwable(String message).

Objects instantiated from Exception or one of its subclasses (except for RuntimeException or one of its subclasses) are checked exceptions.

The RuntimeException class

Exception is directly subclassed by RuntimeException, which describes an exception most likely arising from poorly written code. RuntimeException declares the same constructors (with identical parameter lists) as Exception, and each constructor invokes its Exception counterpart. RuntimeException inherits Throwable's methods. It declares no new methods.

Java provides many exception classes that directly subclass RuntimeException. The following examples are all members of the java.lang package:

  • ArithmeticException signals an illegal arithmetic operation, such as attempting to divide an integer by 0.
  • IllegalArgumentException signals that an illegal or inappropriate argument has been passed to a method.
  • NullPointerException signals an attempt to invoke a method or access an instance field via the null reference.

Objects instantiated from RuntimeException or one of its subclasses are unchecked exceptions.

The Error class

Throwable's other direct subclass is Error, which describes a serious (even abnormal) problem that a reasonable application should not try to handle--such as running out of memory, overflowing the JVM's stack, or attempting to load a class that cannot be found. Like Exception, Error declares identical constructors to Throwable, inherits Throwable's methods, and doesn't declare any of its own methods.

You can identify Error subclasses from the convention that their class names end with Error. Examples include OutOfMemoryError, LinkageError, and StackOverflowError. All three types belong to the java.lang package.

Throwing exceptions

A C library function notifies calling code of an exception by setting the global errno variable to an error code and returning a failure code. In contrast, a Java method throws an object. Knowing how and when to throw exceptions is an essential aspect of effective Java programming. Throwing an exception involves two basic steps:

  1. Use the throw statement to throw an exception object.
  2. Use the throws clause to inform the compiler.

Later sections will focus on catching exceptions and cleaning up after them, but first let's learn more about throwables.

The throw statement

Java provides the throw statement to throw an object that describes an exception. Here's the syntax of the throw statement :

throw throwable;

The object identified by throwable is an instance of Throwable or any of its subclasses. However, you usually only throw objects instantiated from subclasses of Exception or RuntimeException. Here are a couple of examples:

throw new FileNotFoundException("unable to find file " + filename); throw new IllegalArgumentException("argument passed to count is less than zero");

The throwable is thrown from the current method to the JVM, which checks this method for a suitable handler. If not found, the JVM unwinds the method-call stack, looking for the closest calling method that can handle the exception described by the throwable. If it finds this method, it passes the throwable to the method's handler, whose code is executed to handle the exception. If no method is found to handle the exception, the JVM terminates with a suitable message.

The throws clause

You need to inform the compiler when you throw a checked exception out of a method. Do this by appending a throws clause to the method's header. This clause has the following syntax:

throws checkedExceptionClassName (, checkedExceptionClassName)*

A throws clause consists of keyword throws followed by a comma-separated list of the class names of checked exceptions thrown out of the method. Here is an example:

public static void main(String[] args) throws ClassNotFoundException { if (args.length != 1) { System.err.println("usage: java ... classfile"); return; } Class.forName(args[0]); }

This example attempts to load a classfile identified by a command-line argument. If Class.forName() cannot find the classfile, it throws a java.lang.ClassNotFoundException object, which is a checked exception.

Checked exception controversy

The throws clause and checked exceptions are controversial. Many developers hate being forced to specify throws or handle the checked exception(s). Learn more about this from my Are checked exceptions good or bad? blog post.