Java 简明教程

Java - Exceptions

What Is an Exception in Java?

异常(或特殊事件)是在程序执行期间产生的问题。当 Exception 发生时,程序的正常流程会中断,程序/应用程序异常终止,这是不建议的,因此,这些异常需要被处理。

Why Exception Occurs?

异常可能由于许多不同的原因而发生。以下是一些发生异常的情况。

  1. 用户输入了无效的数据。

  2. 找不到需要打开的文件。

  3. 在通信过程中网络连接丢失,或 JVM 内存不足。

其中一些异常是由用户错误引起的,另一些是由程序员错误引起的,还有一些是由某种方式失效的物理资源引起的。

Java Exception Categories

基于这些,我们有以下类别的异常。您需要理解它们才能了解 Java 中的异常处理是如何工作的。

  1. Checked exceptions

  2. Unchecked exceptions

  3. Errors

Java Checked Exceptions

检查异常是由编译器在编译时检查(通知)的异常,这些异常也称为编译时异常。这些异常不能简单地被忽略,程序员应该处理(处理)这些异常。

例如,如果您在程序中使用 FileReader 类从文件中读取数据,如果其构造函数中指定的文件不存在,则会发生 FileNotFoundException,并且编译器会提示程序员处理该异常。

import java.io.File;
import java.io.FileReader;

public class FilenotFound_Demo {

   public static void main(String args[]) {
      File file = new File("E://file.txt");
      FileReader fr = new FileReader(file);
   }
}

如果您尝试编译上述程序,您将获得以下异常。

Output

C:\>javac FilenotFound_Demo.java
FilenotFound_Demo.java:8: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
      FileReader fr = new FileReader(file);
                      ^
1 error

Note − 由于 FileReader 类的 read()close() 方法会抛出 IOException,您可以观察到编译器通知处理 IOException,以及 FileNotFoundException。

Java Unchecked Exceptions

未检查异常是发生在执行时的异常。这些异常也称为运行时异常。其中包括编程错误,例如逻辑错误或不当使用 API。运行时异常在编译时会被忽略。

例如,如果您在程序中声明了一个长度为 5 的数组,并尝试调用该数组的第 6 个元素,则会发生 ArrayIndexOutOfBoundsException 异常。

public class Unchecked_Demo {

   public static void main(String args[]) {
      int num[] = {1, 2, 3, 4};
      System.out.println(num[5]);
   }
}

如果您编译并执行上述程序,您将获得以下异常。

Output

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
	at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)

Java Errors

这些根本不是异常,而是发生在用户或程序员控制范围之外的问题。错误通常在您的代码中被忽略,因为您很少能对错误做任何事情。例如,如果发生堆栈溢出,则会产生一个错误。它们也在编译时被忽略。

Java Exception Hierarchy

所有异常类都是 java.lang.Exception 类的子类型。异常类是类的子类。除异常类之外,还有一个子类称为 Error,它源自 Throwable 类。

错误是发生在严重故障的情况下的异常条件,这些错误不受 Java 程序处理。生成错误是为了指示由运行时环境生成的错误。示例:JVM 已用完内存。通常,程序无法从错误中恢复。

Exception 类有两个主要子类:IOException 类和 RuntimeException 类。

exceptions1

以下列出了最常见的 Java’s Built-in Exceptions检查项和未检查项。

Java Exception Class Methods

以下是 Throwable 类中提供的重要方法的列表。

Sr.No.

Method & Description

1

public String getMessage() 返回有关已发生异常的详细信息。该消息在 Throwable 构造函数中初始化。

2

public Throwable getCause() 如 Throwable 对象表示的那样,返回异常的根源。

3

public String toString() 返回与 getMessage() 结果连接的类名。

4

public void printStackTrace() 将 toString() 的结果和堆栈跟踪打印到 System.err 中,即错误输出流。

5

public StackTraceElement [] getStackTrace() 返回一个包含堆栈跟踪上的每个元素的数组。索引 0 处的元素表示调用堆栈的顶部,而数组中的最后一个元素表示调用堆栈底部的函数。

6

public Throwable fillInStackTrace() 使用当前堆栈跟踪填充 Throwable 对象的堆栈跟踪,同时添加到堆栈跟踪中的任何先前信息。

Catching Exceptions: Exception Handling in Java

使用 trycatch 关键字组合的方法捕获异常。一个 try/catch 块被放置在可能生成异常的代码周围。try/catch 块内的代码被称为受保护代码,try/catch 使用的语法如下所示 −

Syntax

try {
   // Protected code
} catch (ExceptionName e1) {
   // Catch block
}

容易产生异常的代码被放置在 try 块中。当异常发生时,该发生的异常由与它关联的 catch 块处理。每个 try 块应该紧接着一个 catch 块或 finally 块。

catch 语句涉及声明你试图捕获的异常类型。如果受保护代码中发生异常,则检查 try 之后的 catch 块(或块)。如果发生的异常类型被列在一个 catch 块中,则异常被传递到 catch 块中,就像一个参数被传递到一个方法参数中一样。

Example: Demonstrating Exception Handling

在下面的示例中,一个数组被声明为带有 2 个元素。然后代码尝试访问数组的第 3 个元素,从而抛出一个异常。

// File Name : ExcepTest.java
import java.io.*;

public class ExcepTest {

   public static void main(String args[]) {
      try {
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      } catch (ArrayIndexOutOfBoundsException e) {
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}

Output

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

Multiple Catch Blocks

一个 try 块可以跟随多个 catch 块。多个 catch 块的语法如下所示 −

Syntax

try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}

前面的语句演示了三个 catch 块,但是你可以在一个 try 之后可以有任意数量的 catch 块。如果受保护代码中发生异常,则异常被抛到列表中的第一个 catch 块。如果抛出的异常的数据类型匹配 ExceptionType1,则异常在该处被捕获。如果没有,则异常传递到第二个 catch 语句。这将持续到异常被捕获或者通过所有 catch,在这种情况下,当前方法停止执行并且异常被抛到调用堆栈上先前的那个方法。

Example

这里有一个代码段显示如何使用多个 try/catch 语句。

try {
   file = new FileInputStream(fileName);
   x = (byte) file.read();
} catch (IOException i) {
   i.printStackTrace();
   return -1;
} catch (FileNotFoundException f) // Not valid! {
   f.printStackTrace();
   return -1;
}

Catching Multiple Type of Exceptions

自 Java 7 起,你可以使用一个 catch 块处理多个异常,这个功能简化了代码。以下是处理它的方法 −

catch (IOException|FileNotFoundException ex) {
   logger.log(ex);
   throw ex;

The Throws/Throw Keywords

如果一个方法没有处理已检查异常,则方法必须使用 throws 关键字声明它。throws 关键字显示在方法签名的末尾。

你可以使用 throw 关键字抛出一个异常,它既可以是一个新实例化的异常,也可以是你刚捕获的异常。

尝试理解 throws 和 throw 关键字之间的差异,throws 用于推迟对已检查异常的处理,而 throw 用于显式地调用一个异常。

以下方法声明它抛出一个 RemoteException −

Example

import java.io.*;
public class className {

   public void deposit(double amount) throws RemoteException {
      // Method implementation
      throw new RemoteException();
   }
   // Remainder of class definition
}

一个方法可以声明它抛出多个异常,在这种情况下,异常被声明在一个以逗号分隔的列表中。例如,以下方法声明它抛出 RemoteException 和 InsufficientFundsException −

Example

import java.io.*;
public class className {

   public void withdraw(double amount) throws RemoteException,
      InsufficientFundsException {
      // Method implementation
   }
   // Remainder of class definition
}

The Finally Block

finally 块跟随一个 try 块或一个 catch 块。无论异常是否发生,finally 代码块始终执行。

使用 finally 块允许你运行你想要执行的任何清理类型语句,无论受保护代码中发生什么。

finally 块出现在 catch 块的末尾并且具有以下语法 −

Syntax

try {
   // Protected code
} catch (ExceptionType1 e1) {
   // Catch block
} catch (ExceptionType2 e2) {
   // Catch block
} catch (ExceptionType3 e3) {
   // Catch block
}finally {
   // The finally block always executes.
}

Example

public class ExcepTest {

   public static void main(String args[]) {
      int a[] = new int[2];
      try {
         System.out.println("Access element three :" + a[3]);
      } catch (ArrayIndexOutOfBoundsException e) {
         System.out.println("Exception thrown  :" + e);
      }finally {
         a[0] = 6;
         System.out.println("First element value: " + a[0]);
         System.out.println("The finally statement is executed");
      }
   }
}

Output

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed

注意以下内容 −

  1. catch 子句不能没有 try 语句。

  2. try/catch 块出现时,不必有 finally 子句。

  3. 如果没有 catch 子句或 finally 子句,则不能出现 try 块。

  4. 任何代码不能出现在 try、catch、finally 块之间。

The try-with-resources

通常,当我们使用任何资源(例如流、连接等)时,我们必须使用 finally 块显式关闭它们。在以下程序中,我们正在使用 FileReader 从文件中读取数据,并且我们正在使用 finally 块关闭它。

Example

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class ReadData_Demo {

   public static void main(String args[]) {
      FileReader fr = null;
      try {
         File file = new File("file.txt");
         fr = new FileReader(file); char [] a = new char[50];
         fr.read(a);   // reads the content to the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }finally {
         try {
            fr.close();
         } catch (IOException ex) {
            ex.printStackTrace();
         }
      }
   }
}

try-with-resources(也称为 automatic resource management)是 Java 7 引入的一种新的异常处理机制,它会自动关闭 try catch 块内使用的资源。

要使用此语句,您只需在括号中声明所需资源,创建的资源将在块结束时自动关闭。以下是 try-with-resources 语句的语法。

Syntax

try(FileReader fr = new FileReader("file path")) {
   // use the resource
   } catch () {
      // body of catch
   }
}

以下是使用 try-with-resources 语句读取文件中的数据的程序。

Example

import java.io.FileReader;
import java.io.IOException;

public class Try_withDemo {

   public static void main(String args[]) {
      try(FileReader fr = new FileReader("E://file.txt")) {
         char [] a = new char[50];
         fr.read(a);   // reads the contentto the array
         for(char c : a)
         System.out.print(c);   // prints the characters one by one
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

在使用 try-with-resources 语句时,需要注意以下几点。

  1. 要使用带有 try-with-resources 语句的类,它应该实现 AutoCloseable 接口并自动在运行时调用它的 close() 方法。

  2. 你可以在 try-with-resources 语句中声明多个类。

  3. 当你在 try-with-resources 语句的 try 块中声明多个类时,这些类将按相反的顺序关闭。

  4. 除了括号内的资源声明之外,其他所有内容都与 try 块的 normal try/catch 块相同。

  5. 在 try 块中声明的资源在 try-block 开始之前被实例化。

  6. 在 try 块中声明的资源隐式声明为 final。

User-defined Exceptions in Java

您可以在 Java 中创建自己的异常。在编写自己的异常类时,牢记以下几点:

  1. 所有异常都必须是 Throwable 的子类。

  2. 如果你想编写一个自动受到 Handle 或 Declare 规则强制执行的检查异常,则需要扩展 Exception 类。

  3. 如果您想编写运行时异常,则需要扩展 RuntimeException 类。

Syntax

我们可以按如下方式定义自己的 Exception 类:

class MyException extends Exception {
}

您只需要扩展预定义的 Exception 类即可创建您自己的 Exception。这些被认为是已检查的异常。以下 InsufficientFundsException 类是一个用户定义的异常,它扩展了 Exception 类,使其成为一个已检查的异常。异常类就像任何其他类,包含有用的字段和方法。

Example: Creating user-defined exception

// File Name InsufficientFundsException.java
import java.io.*;

public class InsufficientFundsException extends Exception {
   private double amount;

   public InsufficientFundsException(double amount) {
      this.amount = amount;
   }

   public double getAmount() {
      return amount;
   }
}

为了演示如何使用我们用户定义的异常,以下 CheckingAccount 类包含一个 withdraw() 方法,该方法会抛出一个 InsufficientFundsException。

// File Name CheckingAccount.java
import java.io.*;

public class CheckingAccount {
   private double balance;
   private int number;

   public CheckingAccount(int number) {
      this.number = number;
   }

   public void deposit(double amount) {
      balance += amount;
   }

   public void withdraw(double amount) throws InsufficientFundsException {
      if(amount <= balance) {
         balance -= amount;
      }else {
         double needs = amount - balance;
         throw new InsufficientFundsException(needs);
      }
   }

   public double getBalance() {
      return balance;
   }

   public int getNumber() {
      return number;
   }
}

以下 BankDemo 程序演示如何调用 CheckingAccount 的 deposit() 和 withdraw() 方法。

// File Name BankDemo.java
public class BankDemo {

   public static void main(String [] args) {
      CheckingAccount c = new CheckingAccount(101);
      System.out.println("Depositing $500...");
      c.deposit(500.00);

      try {
         System.out.println("\nWithdrawing $100...");
         c.withdraw(100.00);
         System.out.println("\nWithdrawing $600...");
         c.withdraw(600.00);
      } catch (InsufficientFundsException e) {
         System.out.println("Sorry, but you are short $" + e.getAmount());
         e.printStackTrace();
      }
   }
}

编译以上三个文件并运行 BankDemo。这将产生以下结果:

Output

Depositing $500...

Withdrawing $100...

Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
         at CheckingAccount.withdraw(CheckingAccount.java:25)
         at BankDemo.main(BankDemo.java:13)

Common Java Exceptions

在 Java 中,可以定义两类异常和错误。

  1. JVM Exceptions - 这些是专门或逻辑上由 JVM 抛出的异常/错误。示例:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。

  2. Programmatic Exceptions - 这些异常是由应用程序或 API 程序员显式抛出的。示例:IllegalArgumentException、IllegalStateException。