Java 中的异常详解
异常的定义
定义: 异常(Exception)是一个发生在程序运行中的事件。它会破坏程序指令流的正常运行。
异常的分类
异常经常被分为3个不同的种类:
- The checked exception (受检异常)
- The runtime exception
- The error
受检异常在正常的程序编写中,软件会提醒程序员对相关代码命令作异常处理,如:调用 java.io.FileReader
中的相关方法。
但有些异常并不会在编写代码时得到提示,如:NullPointerException
。这种报错一般情况下都会在我们执行代码的时候报错的控制台。这种就属于 the runtime exception。
而error多是由于外部因素引起而非代码本身,如:IO口调用失败。
∗
*
∗ error 和 runtime exception 都无法被提前预知并做相应的处理,所以被成为“非受检异常”
异常处理
通常,在Java中处理抛出异常的方法有两种:
- try … catch
- 方法中提供 throws 条款
(官方文档用词是 throws clause,不知这里翻译为throws 条款是否正确)
try … catch
基本写法
// 基本结构
try{
// 基本逻辑代码
} catch(异常 e) {
// 对于异常的处理方法
} finally {
// 一定会执行的代码
}
这个结构可以根据具体需要进行省略一些内容:
// 省略 finally 部分
try {
} catch() {
}
// 省略 catch 部分
try {
} finally {
}
当遇到要同时捕捉多个异常的时候,就可以增加 catch 部分的数量来实现。
对每一个捕捉到的异常做相应的处理。但如果所有需要捕捉的异常都可以用相同的方式进行处理,我们可以把需要捕捉的异常全部放在一个catch中,具体写法如下:
// 每种异常都做不同处理
try{
// 基本逻辑代码
} catch(异常1 e) {
// 对于异常1的处理方法
} catch(异常2 e) {
// 对于异常1的处理方法
}
// 所有异常一起处理
try {
} catch(异常1 | 异常2 | 异常3 e) {
// 异常1、2、3共同的处理方法
}
运行顺序
以下面一段代码为例
public class Test {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int age = 0;
try {
System.out.println("Please enter an age:");
age = in.nextInt();
System.out.println("age assigned successfully");
} catch (Exception e) {
System.out.println("The input age is invalid.");
} finally {
System.out.println("final");
}
in.close();
}
}
代码结果输出如下:
不难看出,这里使用的是基本的 try … catch 结构,就是用try后的代码块执行基本的逻辑代码。如果输入12,12为数字则不会产生异常。在执行完 try中的语句后t跳过catch执行finally中的代码。
如果在该部分代码运行中有异常出现,则用catch捕捉,程序从异常出现的语句停止,跳转到catch部分,之后由catch后的代码块执行对捕捉到的异常进行的处理。最后执行finally中的代码。
当 try 后的代码块中有return时
public class Test {
public static int func() {
int age = 0;
try {
age = 6;
System.out.println("try: " + age);
return age;
} catch (Exception e) {
} finally {
age = 8;
System.out.println("final: " + age);
}
return -1;
}
public static void main(String[] args) {
System.out.println("main: " + func());
}
}
输出:
根据输出可以得到代码的执行顺序为: try -> finally -> main。由此可知,代码在 return 前先要执行 finally 后的代码块。
但这里finally中对age赋值为 8,并没有返回到main中。原因是:在 func 方法执行到 try 中的 return 时,会对此时栈中 age 的值进行备份用以在后面执行 return 操作,而此时 age 的值为6。在这之后程序运行 finally 中的赋值语句,把 age 从 6 赋为 8。
throws 条款
throws
关键字多于抛出传参时因参数导致的异常。
public static int func(String x) throws NumberFormatException {
return Integer.parseInt(x);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String x = sc.nextLine();
try {
System.out.println("main: " + func(x));
} catch (NumberFormatException e) {
System.out.println("\"x\" cannot convert to integer");
}
sc.close();
}
当把 x
赋值为 a
时,输出:
在正常情况下,这个在输入的 x
无法转为 integer 的情况下,就会出现 NumberFormateException
的异常。此时我们可以用方法中抛出异常,再在上层的代码中进行异常处理。否则,程序会在 Integer.parseInt(x);
执行时报错。
关键字 throw
我们在代码中除了会看到 throws 关键字,还会看到 throw 关键字。
throw关键字,通常情况下用于在代码中创建自己的独有异常。
1. 根据已有的异常类抛出相应的异常
public static void ageCheck(int age) throws RuntimeException {
if(age < 16)
throw new RuntimeException("he is too young!");
System.out.println("he is an adult.");
}
public static void main(String[] args) {
ageCheck(18);
ageCheck(5);
}
输出:
改异常为Runtime Exception,是非受检异常,所以可以直接抛出。
如果为 创建的是 Exception 就必须要在调用方法时用 try_catch 处理,因为该异常为受检异常。
public static void ageCheck(int age) throws Exception {
if(age < 16)
throw new Exception("he is too young!");
System.out.println("he is an adult.");
}
public static void main(String[] args) {
try {
ageCheck2(18);
ageCheck2(5);
} catch (Exception e) {
System.out.println("I am 5, i am too young.");
}
}
输出:
2. 创建新异常
创建一个异常,需要创建一个新的 class,再根据需要看是继承 Exception,还是 RuntimeException,这两个的区别就在于继承 Exception 的异常会被认为是 受检异常,而继承 RuntimeException 的异常是非受检异常。
class RangeException extends Exception {
public RangeException(String message) {
super(message);
}
}
public class Test {
public static void ageCheck2(int age) throws RangeException {
if(age < 16) {
RangeException e = new RangeException("he is too young!");
throw e;
}
System.out.println("he is an adult.");
}
public static void main(String[] args) {
try {
ageCheck2(18);
ageCheck2(5);
} catch (Exception e) {
System.out.println("I am 5, i am too young.");
}
}
}