异常是什么?
异常(或异常事件)是在程序执行期间出现的问题。当异常发生时,程序的正常流程会被中断并且程序/应用会异常终止,这是不建议的情况,因此,这些异常需要被处理。
为什么会出现异常?
异常可能由于多种不同的原因而发生。以下是一些可能出现异常的情形:
其中一些异常是由用户错误引起的,另一些则是由于编程错误,还有一些则是由于物理资源出现了某种形式的故障。
Java 异常类别
基于以上情况,我们有以下几种异常类别。理解这些可以帮助了解 Java 中的异常处理工作原理。
已检查异常(Checked exceptions)
已检查异常是编译器在编译时检查(通知)的异常,这些也被称为编译时异常。这些异常不能被简单忽略,程序员必须处理(处理)这些异常。
示例: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://file.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
。
未检查异常(Unchecked exceptions)
未检查异常是在执行时发生的异常。这些也被称作运行时异常。这些包括编程错误,如逻辑错误或不当使用 API。运行时异常在编译时会被忽略。
示例:Java 中的未检查异常
例如,如果你在程序中声明了一个大小为 5 的数组,并试图调用该数组的第 6 个元素,则会引发 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)
错误(Errors)
这些不是异常,而是超出用户或程序员控制的问题。错误通常在你的代码中被忽略,因为你很少能对错误做些什么。例如,如果发生栈溢出,就会产生一个错误。在编译时也会被忽略。
Java 异常层次结构
所有的异常类都是 java.lang.Exception
类的子类。Exception
类是 Throwable
类的一个子类。除了 Exception
类之外,还有一个名为 Error
的子类也派生于 Throwable
类。
错误是发生在严重失败的情况下异常条件,这些在 Java 程序中通常不会被处理。错误是用来指示由运行环境生成的错误。例如:JVM 内存不足。通常,程序不能从错误中恢复。
Exception
类有两个主要子类:IOException
类和 RuntimeException
类。
Java 内置异常列表
以下是最常见的已检查和未检查的 Java 内置异常列表。
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 对象的堆栈跟踪,添加到堆栈跟踪中的任何先前信息。 |
捕捉异常:Java 中的异常处理
一个方法使用 try
和 catch
关键字组合来捕捉异常。try/catch
块放置在可能生成异常的代码周围。位于 try/catch
块中的代码被称为受保护的代码,使用 try/catch
的语法如下所示:
语法
try {
} catch (ExceptionName e1) {
}
可能会抛出异常的代码放在 try
块中。当异常发生时,该异常被相应的 catch
块处理。每个 try
块后面应该紧跟一个 catch
块或 finally
块。
捕获语句涉及声明你要捕获的异常类型。如果受保护的代码中发生了异常,随后的 catch
块(或多个 catch
块)将被检查。如果发生异常的类型列在某个 catch
块中,异常则传递给该 catch
块,如同参数传递到方法参数中一样。
示例:演示异常处理
在下面的例子中,声明了一个具有两个元素的数组。然后代码试图访问数组的第三个元素,这将引发异常。
import java.io.*;
public class ExcepTest {
public static void main(String args[]) {
try {
int a[] = new int[2];
System.out.println("访问第三个元素:" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("异常:" + e);
}
System.out.println("超出捕获块范围");
}
}
输出:
异常:java.lang.ArrayIndexOutOfBoundsException: 3
超出捕获块范围
多个捕获块
一个 try
块后面可以跟多个 catch
块。多个 catch
块的语法如下:
语法
try {
} catch (ExceptionType1 e1) {
} catch (ExceptionType2 e2) {
} catch (ExceptionType3 e3) {
}
前面的语句演示了三个 catch
块,但你可以在一个单一的 try
后面有任意数量的 catch
块。如果受保护的代码中发生了异常,异常将被抛给列表中的第一个 catch
块。如果抛出的异常类型匹配 ExceptionType1
,则在那里被捕获。如果不匹配,则异常传递给第二个 catch
语句。这种情况一直持续到异常被捕获或者穿过所有 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
。
Java 中的异常
异常是什么?
异常(或异常事件)是在程序执行期间出现的问题。当异常发生时,程序的正常流程会被中断并且程序/应用会异常终止,这是不建议的情况,因此,这些异常需要被处理。
为什么会出现异常?
异常可能由于多种不同的原因而发生。以下是一些可能出现异常的情形:
其中一些异常是由用户错误引起的,另一些则是由于编程错误,还有一些则是由于物理资源出现了某种形式的故障。
Java 异常类别
基于以上情况,我们有以下几种异常类别。理解这些可以帮助了解 Java 中的异常处理工作原理。
已检查异常(Checked exceptions)
已检查异常是编译器在编译时检查(通知)的异常,这些也被称为编译时异常。这些异常不能被简单忽略,程序员必须处理(处理)这些异常。
示例: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://file.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
。
未检查异常(Unchecked exceptions)
未检查异常是在执行时发生的异常。这些也被称作运行时异常。这些包括编程错误,如逻辑错误或不当使用 API。运行时异常在编译时会被忽略。
示例:Java 中的未检查异常
例如,如果你在程序中声明了一个大小为 5 的数组,并试图调用该数组的第 6 个元素,则会引发 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)
错误(Errors)
这些不是异常,而是超出用户或程序员控制的问题。错误通常在你的代码中被忽略,因为你很少能对错误做些什么。例如,如果发生栈溢出,就会产生一个错误。在编译时也会被忽略。
Java 异常层次结构
所有的异常类都是 java.lang.Exception
类的子类。Exception
类是 Throwable
类的一个子类。除了 Exception
类之外,还有一个名为 Error
的子类也派生于 Throwable
类。
错误是发生在严重失败的情况下异常条件,这些在 Java 程序中通常不会被处理。错误是用来指示由运行环境生成的错误。例如:JVM 内存不足。通常,程序不能从错误中恢复。
Exception
类有两个主要子类:IOException
类和 RuntimeException
类。
Java 内置异常列表
以下是最常见的已检查和未检查的 Java 内置异常列表。
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 对象的堆栈跟踪,添加到堆栈跟踪中的任何先前信息。 |
捕捉异常:Java 中的异常处理
一个方法使用 try
和 catch
关键字组合来捕捉异常。try/catch
块放置在可能生成异常的代码周围。位于 try/catch
块中的代码被称为受保护的代码,使用 try/catch
的语法如下所示:
语法
try {
} catch (ExceptionName e1) {
}
可能会抛出异常的代码放在 try
块中。当异常发生时,该异常被相应的 catch
块处理。每个 try
块后面应该紧跟一个 catch
块或 finally
块。
捕获语句涉及声明你要捕获的异常类型。如果受保护的代码中发生了异常,随后的 catch
块(或多个 catch
块)将被检查。如果发生异常的类型列在某个 catch
块中,异常则传递给该 catch
块,如同参数传递到方法参数中一样。
示例:演示异常处理
在下面的例子中,声明了一个具有两个元素的数组。然后代码试图访问数组的第三个元素,这将引发异常。
import java.io.*;
public class ExcepTest {
public static void main(String args[]) {
int a[] = new int[2];
try {
System.out.println("访问第三个元素:" + a[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("异常:" + e);
} finally {
a[0] = 6;
System.out.println("第一个元素值:" + a[0]);
System.out.println("finally 子句被执行");
}
}
}
输出:
异常:java.lang.ArrayIndexOutOfBoundsException: 3
第一个元素值:6
finally 子句被执行
终于块(Finally Block)
finally
块跟随 try
块或 catch
块。无论是否发生异常,finally
块中的代码总是被执行。
使用 finally
块允许你运行任何你希望执行的清理型语句,不管在受保护的代码中发生了什么。
finally
块出现在 catch
块的末尾,具有以下语法:
语法
try {
} catch (ExceptionType1 e1) {
} catch (ExceptionType2 e2) {
} catch (ExceptionType3 e3) {
} finally {
}
注意事项
-
-
当
try/catch
块存在时,并不一定需要有 finally
子句。
-
try
块不能没有 catch
子句或 finally
子句。
-
任何代码不能存在于
try
, catch
, finally
块之间。
使用带有资源的 try 语句(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 (IOException e) {
}
示例
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);
for(char c : a)
System.out.print(c);
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意事项
-
要使用
try-with-resources
语句,类需要实现 AutoCloseable
接口,并且它的 close()
方法会在运行时自动被调用。
-
你可以在
try-with-resources
语句中声明多个类。
-
当你在
try-with-resources
语句的 try
块中声明多个类时,这些类会以相反的顺序关闭。
-
除了括号内的资源声明外,其他一切与正常的
try/catch
块相同。
-
-
在
try
块中声明的资源隐式地被声明为 final
。
Java 中的自定义异常
你可以在 Java 中创建自己的异常。编写自己的异常类时,请记住以下几点:
-
-
如果你想写一个已检查异常,该异常由处理或声明规则自动强制执行,你需要扩展
Exception
类。
-
如果你想写一个运行时异常,你需要扩展
RuntimeException
类。
语法
我们可以定义自己的异常类如下:
class MyException extends Exception {
}
你只需要扩展预定义的 Exception
类来创建自己的异常。这些都是已检查异常。下面的 InsufficientFundsException
类是一个扩展 Exception
类的自定义异常,使其成为一个已检查异常。异常类就像其他任何类一样,包含有用的字段和方法。
示例:创建自定义异常
import java.io.*;
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
为了演示使用我们自定义的异常,下面的 CheckingAccount
类包含一个抛出 InsufficientFundsException
的 withdraw()
方法。
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()
方法。
public class BankDemo {
public static void main(String [] args) {
CheckingAccount c = new CheckingAccount(101);
System.out.println("存款 $500...");
c.deposit(500.00);
try {
System.out.println("\n取款 $100...");
c.withdraw(100.00);
System.out.println("\n取款 $600...");
c.withdraw(600.00);
} catch (InsufficientFundsException e) {
System.out.println("对不起,您缺少 $" + e.getAmount());
e.printStackTrace();
}
}
}
编译上述三个文件并运行 BankDemo
。这将产生以下结果:
Output
存款 $500...
取款 $100...
取款 $600...
对不起,您缺少 $200.0
InsufficientFundsException
at CheckingAccount.withdraw(CheckingAccount.java:25)
at BankDemo.main(BankDemo.java:13)
常见的 Java 异常
在 Java 中,可以定义两类异常和错误:
-
JVM 异常 — 这些是专由 JVM 逻辑上抛出的异常/错误。例子:
NullPointerException
、ArrayIndexOutOfBoundsException
、ClassCastException
。
-
程序性异常 — 这些异常是由应用程序或 API 编程者显式抛出的。例子:
IllegalArgumentException
、IllegalStateException
。