异常(或异常事件)是程序执行过程中出现的问题。当异常发生时,程序的正常流程被打乱,程序或应用程序异常终止,不建议这样做,因此,要处理这些异常。
异常可能由于许多不同的原因而发生。以下是发生异常的一些场景。
用户输入了无效数据。
找不到需要打开的文件。
通信过程中网络连接丢失或 JVM 内存不足。
其中一些异常是由用户错误引起的,另一些是由程序员错误引起的,还有一些是由以某种方式发生故障的物理资源引起的。
基于这些,我们有三个例外的类别。您需要了解它们才能了解 Java 中异常处理的工作原理。
检查异常 - 检查异常是一种异常由编译器在编译时检查(通知),这些也称为编译时异常。这些异常不能简单地被忽略,程序员应该照顾(处理)这些异常。
例如,如果您在中使用 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://文件.txt");
FileReader fr = new FileReader(file);
}
}
如果尝试编译上述程序,您将得到以下异常。
输出
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
注意 − 由于 FileReader 类的方法 read() 和 close() 会抛出 IOException,因此您可以观察到编译器通知处理 IOException 以及 FileNotFoundException。
未检查异常 - 未检查异常是在执行时发生的异常。这些也称为运行时异常。其中包括编程错误,例如逻辑错误或 API 使用不当。编译时会忽略运行时异常。
例如,如果您在程序中声明了一个大小为 4 的数组,并尝试调用数组中的第5个元素,就会发生ArrayIndexOutOfBoundsException异常。
示例
public class Unchecked_Demo {
public static void main(String args[]) {
int num[] = {1, 2, 3, 4};
System.out.println(num[5]);
}
}
如果编译并执行上述程序,您将得到以下异常。
输出
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
at Exceptions.Unchecked_Demo.main(Unchecked_Demo.java:8)
错误 - 这些根本不是异常,而是出现的问题超出用户或程序员的控制。代码中的错误通常会被忽略,因为您很少对错误采取任何措施。例如,如果发生堆栈溢出,就会出现错误。它们在编译时也会被忽略。
异常层次结构
所有异常类都是 java.lang.Exception 类的子类型。异常类是 Throwable 类的子类。除了异常类之外,还有另一个子类Error,它派生自Throwable类。
错误是在严重故障的情况下发生的异常情况,这些情况不由Java程序处理。生成错误表示运行时环境产生的错误。示例:JVM 内存不足。通常情况下,程序无法从错误中恢复。
Exception 类有两个主要子类:IOException 类和 RuntimeException 类。
以下是最常见的已检查和未检查Java 内置异常的列表。
异常方法
以下是 Throwable 类中可用的重要方法的列表。
序号 | 方法及说明 |
---|---|
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 对象的堆栈跟踪,添加到堆栈跟踪。 |
捕获异常
使用 try 和 catch 关键字的组合来捕获异常。 try/catch 块放置在可能生成异常的代码周围。 try/catch 块中的代码称为受保护代码,使用 try/catch 的语法如下所示
语法
try {
//受保护的代码
} catch (ExceptionName e1) {
// 捕获块
}
容易出现异常的代码被放置在 try 块中。当异常发生时,发生的异常由与其关联的 catch 块处理。每个 try 块后面都应该紧跟一个 catch 块或finally 块。
catch 语句涉及声明您要捕获的异常类型。如果受保护的代码中发生异常,则检查 try 后面的 catch 块(或多个块)。如果发生的异常类型在 catch 块中列出,则异常将被传递到 catch 块,就像将参数传递到方法参数一样。
示例
如下例如,声明一个数组有 2 个元素。然后代码尝试访问抛出异常的数组的第三个rd元素。
// 文件名: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");
}
}
输出
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block
多个捕获块
一个 try 块后面可以跟多个 catch 块。多个 catch 块的语法如下所示 -
语法
try {
//受保护的代码
} catch (ExceptionType1 e1) {
// 捕获块
} catch (ExceptionType2 e2) {
// 捕获块
} catch (ExceptionType3 e3) {
// 捕获块
}
前面的语句演示了三个 catch 块,但一次尝试后可以拥有任意数量的 catch 块。如果受保护的代码中发生异常,则该异常将被抛出到列表中的第一个 catch 块。如果抛出的异常的数据类型与 ExceptionType1 匹配,则会在那里捕获该异常。如果不是,则异常将传递到第二个 catch 语句。这将持续下去,直到异常被捕获或通过所有捕获,在这种情况下,当前方法停止执行,并将异常抛出到调用堆栈上的前一个方法。
示例
这是显示如何使用多个 try/catch 语句的代码段。
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch (IOException i) {
i.printStackTrace();
return -1;
} catch (FileNotFoundException f) // 无效! {
f.printStackTrace();
return -1;
}
捕获多种类型的异常
从 Java 7 开始,您可以使用单个异常处理多个异常catch 块,此功能简化了代码。下面是如何做到这一点
catch (IOException|FileNotFoundException ex) {
logger.log(ex);
throw ex;
Throws/Throw 关键字
如果方法不处理受检查的异常,则该方法必须使用 throws关键字来声明它。 throws 关键字出现在方法签名的末尾。
您可以使用 throw 关键字抛出异常,可以是新实例化的异常,也可以是刚刚捕获的异常.
尝试理解 throws 和 throw 关键字之间的区别,throws 用于推迟检查异常的处理,而 throw 用于调用显式异常。
以下方法声明它抛出 RemoteException
示例
import java.io.*;
public class className {
public void deposit(double amount) throws RemoteException {
//方法实现
throw new RemoteException();
}
// 类定义的其余部分
}
一个方法可以声明它抛出多个异常,在在这种情况下,异常在以逗号分隔的列表中声明。例如,以下方法声明它抛出 RemoteException 和 InsufficientFundsException -
示例
import java.io.*;
public class className {
public void withdraw(double amount) throws RemoteException,
InsufficientFundsException {
//方法实现
}
// 类定义的其余部分
}
Finally 块
Thefinally 块位于 try 块之后或一个 catch 块。无论是否发生异常,finally 代码块始终都会执行。
使用finally 块允许您运行要执行的任何清理类型语句,无论受保护代码中发生什么情况。
finally 块出现在 catch 块的末尾,具有以下语法 -
语法
try {
//受保护的代码
} catch (ExceptionType1 e1) {
// 捕获块
} catch (ExceptionType2 e2) {
// 捕获块
} catch (ExceptionType3 e3) {
// 捕获块
}finally {
//finally 块始终执行。
}
示例
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");
}
}
}
输出
Exception thrown :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed
注意以下几点
如果没有 try 语句,catch 子句就不能存在。
每当存在 try/catch 块时,并不强制必须有finally子句。
如果没有catch子句或finally子句,则不能存在try块。
try、catch、finally 块之间不能出现任何代码。
try-with-resources
通常,当我们使用流、连接等任何资源时,我们必须使用finally块显式关闭它们。在下面的程序中,我们使用 FileReader 从文件中读取数据,并使用 finally 块关闭它。
示例
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); //读取内容到数组
for(char c : a)
System.out.print(c); //将字符一一打印
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fr.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
try -with-resources,也称为自动资源管理,是Java 7中引入的一种新的异常处理机制,它会自动关闭try catch块内使用的资源。
要使用该语句,只需在括号内声明所需的资源,创建的资源将在块末尾自动关闭。以下是 try-with-resources 语句的语法。
语法
try(FileReader fr = new FileReader("file path")) {
//使用资源
} catch () {
// catch 主体
}
}
以下是使用 try-with-resources 语句读取文件中数据的程序。
示例
import java.io.FileReader;
import java.io.IOException;
public class Try_withDemo {
public static void main(String args[]) {
try(FileReader fr = new FileReader("E://文件.txt")) {
char [] a = new char[50];
fr.read(a); //读取内容到数组
for(char c : a)
System.out.print(c); //将字符一一打印
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用 try-with-resources 语句时应牢记以下几点。
To使用带有 try-with-resources 语句的类,它应该实现 AutoCloseable 接口,并且它的 close() 方法在运行时自动调用。
您可以在 try-with-resources 语句中声明多个类。
当您在 try-with-resources 语句中声明多个类时, resources 声明这些类以相反的顺序关闭。
除了括号内的资源声明之外,所有内容与 try 块的正常 try/catch 块相同。
try 中声明的资源在 try 块开始之前实例化。
try 块中声明的资源是隐式声明为final。
用户定义的异常
您可以在Java 中创建自己的异常。在编写自己的异常类时,请记住以下几点 -
所有异常必须是 Throwable 的子级。
如果要编写由 Handle 或 Declare Rule 自动强制执行的已检查异常,则需要扩展 Exception 类。
如果要编写运行时异常,您需要扩展 RuntimeException 类。
我们可以定义自己的 Exception 类,如下所示 -
class MyException extends Exception {
}
您只需扩展预定义的 Exception 类来创建您自己的异常。这些被认为是检查异常。下面的 InsufficientFundsException 类是一个用户定义的异常,它扩展了 Exception 类,使其成为受检查的异常。异常类与任何其他类一样,包含有用的字段和方法。
示例
// 文件名 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。
// 文件名 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() 和 Deposit() 方法。
// 文件名 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。这将产生以下结果 -
输出
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)
常见异常
在 Java 中,可以定义异常和错误两类。
JVM 异常 - 这些是 JVM 专门或逻辑抛出的异常/错误。示例:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。
编程异常 - 这些异常由应用程序或 API 程序员显式抛出。示例:IllegalArgumentException、IllegalStateException。