本节学习目标:
异常是程序中的一些错误。可能产生于程序员没有预料到的各种情况。
比如经典的算术运算异常ArithmeticException
:除数为0。
public class Test {
public static void main(String[] args) {
int i = 1 / 0;
System.out.println("发生异常");
}
}
尝试编译并运行:
发生异常后,产生异常的代码之后的代码都不会执行。
一个异常提示信息包括四个部分:
在Java中,异常和错误是两种不同的概念。
对于一般语义的错误,分为语法错误,运行时错误和逻辑错误三大类:
ArithmeticException
)和常见的空指针异常(NullPointerException
),数组越界异常(ArrayIndexOutOfBoundsException
)等;对于Java语言定义的错误,分为两大类,一个是错误(Error),一个是异常(Exception):
StackOverflowError
),这类错误是致命的,发生这类错误将导致程序不能继续运行,程序不能从这类错误中恢复过来;NullPointerException
)等,这类错误是非致命的,Java可以捕获和处理异常,但不处理也会导致程序的非正常终止。Java定义了一系列的类来描述错误,每个包中都定义了异常类,所有异常类都是Throwable
类的子类,同时Throwable
异常也是最高级别的异常。
Throwable
类有两个子类,分别是Error
类和Exception
类,对应上文提到的错误和异常。Error
类是致命性类,它及其子类用来描述Java运行系统中的内部错误以及资源耗尽的错误等;
Exception
类是非致命性类,可以通过捕捉处理使程序继续正常执行。Exception
类又根据错误发生的原因分为运行时异常(RuntimeException
)异常和其他异常,继承树如下:
为保证程序正常执行,需要对发生的异常进行相应的处理。在Java中,如果某个方法发生了异常,可以在当前方法中进行捕捉并处理。
try-catch语句块是Java语言中常用的异常处理结构:
try {
// 语句块
} catch (异常类 标识符) {
// 异常处理语句
}
由try
关键字包括的语句块内用来编写可能发生异常的代码,关键字catch
用来捕获由try
语句块中可能发生的异常,
需要在catch
关键字后面的括号里定义可能发生的异常,catch
关键字会捕捉try
语句块中产生的此类异常及其子类异常,
然后在catch
语句块中处理异常。
编写代码进行测试:
public class TestTryCatch {
public static void main(String[] args) {
try {
int i = 1 / 0;
System.out.println("发生异常1");
} catch (Exception e) {
e.printStackTrace();
System.out.println("发生异常2");
}
System.out.println("发生异常3");
}
}
尝试运行并编译:
发现第一句输出代码没有执行,但第二句和第三句输出代码执行了。在try
语句块中,一旦发生异常,则try
语句块中的剩余代码都不会执行。
如果catch
关键字捕捉到了try
语句块中产生的异常,则执行catch
语句块中的语句。try-catch语句块之后的语句会正常继续执行。
try-catch-finally语句块是在try-catch语句块的基础上再加上finally
语句块,try-catch-finally语句块是Java中标准的异常处理结构:
try {
// 语句块
} catch (异常类 标识符) {
// 异常处理语句
} finally {
// 语句块
}
编写代码进行测试:
public class TestTryCatchFinally {
public static void main(String[] args) {
try {
int[] i = {
1, 2};
System.out.println(i[2]);
System.out.println("发生异常1");
} catch (Exception e) {
e.printStackTrace();
System.out.println("发生异常2");
} finally {
System.out.println("发生异常3");
}
System.out.println("发生异常4");
}
}
尝试编译并运行:
在关键字finally
语句块中的语句,无论是否发生异常都会执行。完整的异常处理结构一定要包含finally
语句块。
在JDK1.7新加入了try-with-resource语句块,可以自动将调用的资源进行释放。
比如使用Scanner
类进行输入,使用完毕需要调用它的close()
方法关闭:
import java.util.Scanner;
public class TestTryWithResource {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
int i = input.nextInt();
i /= 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
input.close();
}
}
}
上面的try-catch-finally语句块可以使用try-with-resource语句块替换:
import java.util.Scanner;
public class TestTryWithResource {
public static void main(String[] args) {
try (Scanner input = new Scanner(System.in)) {
int i = input.nextInt();
i /= 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
// 语句块
}
}
}
需要使用的资源流(如文件流等,需要实现Closeable
接口)可以在try
关键字后的括号内声明并初始化,无论是否发生异常,都会自动调用资源流的close()
方法安全关闭并释放相关资源。
Java提供了两个关键字throw
和throws
来抛出异常。
throw
关键字用于在实际代码中主动抛出一个异常对象(如果没有异常对象,需要使用new
关键字创建一个异常对象):
import java.util.Scanner;
public class TestThrow {
public static void main(String[] args) {
int age = new Scanner(System.in).nextInt();
if (age <= 0) {
throw new RuntimeException("年龄只能为正整数");
} else {
System.out.println(age);
}
}
}
输入-3
,运行结果:
使用throw
关键字主动抛出的异常也可以被catch
关键字捕获并处理。
在方法中,异常如果不想在当前方法中处理,可以使用throws
关键字抛出,由调用此方法的语句所在的方法中处理:
public class TestThrows {
public static void main(String[] args) {
try {
function();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序继续执行");
}
public static void function() throws ArithmeticException {
int i = 1 / 0;
}
}
调用使用throws
关键字抛出异常(除了RuntimeException
异常及其子类异常)的方法时,必须使用try-catch语句块进行显式处理,
也可以继续使用throws
关键字向上抛出异常*,否则无法通过编译。
main()
方法同样可以使用throws
关键字抛出异常,但可能没有语句去处理从main()
方法中throws
出去的异常。
构造方法也可以使用throws
关键字抛出异常,使用带有throws
关键字的构造方法来创建对象时,需要进行异常处理。
使用Java定义的异常类可以描述大部分异常情况,我们也可以编写自定义异常类来描述其他异常,只需要继承Exception
类或者其子类即可。
编写年龄数字异常类,描述年龄不能为0或者负数:
public class AgeNumberException extends Exception {
public AgeNumberException() {
super("年龄不能为0或负数"); // 可以调用父类的构造方法Exception(String message),传入异常原因描述
}
}
编写代码使用此异常类进行测试:
import java.util.Scanner;
public class CustomException {
public static void main(String[] args) {
try (Scanner input = new Scanner(System.in)) {
int age = input.nextInt();
if (age <= 0) {
throw new AgeNumberException();
}
}
}
}
输入-3,运行结果:
Throwable
类是所有异常类的父类,它定义了很多获取异常信息的方法,子类可以使用。
常用的构造方法(绝大多数子类异常也提供此方法):
Throwable()
:默认构造方法,不提供异常原因。Throwable(String message)
:常用构造方法,可以传入一个字符串作为异常原因。Throwable
类提供的常用方法:
方法 | 返回值 | 功能 |
---|---|---|
getMessage() |
String |
获取异常发生的原因 |
getCause() |
Throwable |
获取异常对象 |
toString() |
String |
获取异常对象的字符串形式(异常类名: 异常原因 ) |
printStackTrace() |
void |
向控制台打印异常的跟踪信息 |