Java的异常和异常处理
学习内容来自于mooc中华东师范大学课程Java核心技术。
Java异常分类
Java 异常是指程序执行中发生的不正常行为,语法错误和逻辑错误不算异常
Throwable是所有错误的祖先
Java异常分为两大类:Error 和 Exception
Error
系统内部错误或资源耗尽,不常见,可以不处理。
Exception
程序有关的异常。分成两大类:RuntimeException、非RuntimeException
- RuntimeException:程序自身的错误,例如空指针、数组越界。这个又叫运行时异常
- 非RuntimeException:外界相关的错误,例如打开不存在的文件、加载不存在的类。这个又叫编译时异常
Checked和Unchecked
Checked和Unchecked也是一种错误的分类方法。
- Unchecked Exception: 编译器不会辅助检查的,需要自己注意的异常。包括Error和RuntimeException子类。如 a/b,b 可能为0,而编译器不会辅助检查。以预防为主。
- Checked Exception:非RuntimeException的子类,编译器会辅助检查。程序员必需处理,以发生后处理为主。
这个是
Java异常分类重点:
- 异常是程序不正常的行为或者状态
- Java异常分为 Exception(程序相关)和 Error(系统相关)
- Java程序相关异常又分为 unchecked(编译器不辅助检查)和 checked(编译器辅助检查)
Java异常处理
异常处理的目的:
- 允许用户及时保存结果
- 抓住异常,分析异常行程原因
- 控制程序返回到安全状态
try-catch-finally机制
有三种结构:
- try…catch(catch可以有多个,下同)
- try…catch…finally
- try…finally 。应用场景:执行一段代码,不管是否发生异常,都必须执行某个业务逻辑。但是如果,执行过程中发生异常,在 finally 执行结束后会直接退出程序
try必须有,catch和finally至少有一个
每种结构体的内容为:
- try:正常的业务逻辑内容
- catch:当 try 发生异常时,执行 catch;否则,不执行
- finally:当 try 或 catch 发生异常后,必须执行 finally
例子:
public static void main(String[] args) {
try {
// 都正确,不会执行catch
int a = 5/2;
System.out.println(a);
}
catch(Exception e) {
e.printStackTrace();
}
finally {
System.out.println("Section 1 is over.");
}
try {
// 不正确,会执行catch
int a = 5/0;
// 出现异常,不执行
System.out.println(a);
}
catch(Exception e) {
e.printStackTrace();
}
finally {
System.out.println("Section 2 is over.");
}
try {
// 不正确,会执行catch
int a = 5/0;
// 出现异常,不执行
System.out.println(a);
}
catch(Exception e) {
e.printStackTrace();
// 会执行,在finally之后报错
int a = 5/0;
}
finally {
System.out.println("Section 3 is over.");
}
}
执行结果:
2
Section 1 is over.
java.lang.ArithmeticException: / by zero
at courseTest.TryDemo.main(TryDemo.java:20)
Section 2 is over.
java.lang.ArithmeticException: / by zero
at courseTest.TryDemo.main(TryDemo.java:33)
Section 3 is over.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at courseTest.TryDemo.main(TryDemo.java:40)
注意:
- 异常发生时,在 try 语句块中异常后面的代码不会执行
- 异常未发生,则不会进入 catch 块
- 不管是否发生异常,finally 块一定执行
多个catch
catch块可以有多个,每个拥有不同的入口形参。当发生的异常和某一个catch形参类型一致,那么将会执行该块内容;若异常和所有的catch形参都不同,那么将会跳过catch部分。
同样的,catch块执行后,将不会跳回到try发生异常的位置,而是去执行下一部分。
catch块的匹配机制和 if 语句相同,都是从上到下匹配。一般将小的异常类型(精准,如具体的异常子类)写在前面,大二宽泛的异常写在后面。
如果不按照这个规则写的话,因为精准的小的异常在大而宽泛的异常之后,该catch块并不会执行,会编译报错Unreachable catch block for ArithmeticException. It is already handled by the catch block for ExceptionJava(553648315)
例子:
public class MultiCatchDemo {
public static void main(String[] args) {
try {
int a = 5/0;
System.out.println("a is: " + a);
}
catch(Exception e) {
e.printStackTrace();
}
// 这样是不对的,ArithmeticException是小异常,应该卸载Exception前面
catch(ArithmeticException e) {
e.printStackTrace();
}
finally {
System.out.println("Section 1 is over");
}
}
}
更改为:
public static void main(String[] args) {
try {
int a = 5/0;
System.out.println("a is: " + a);
}
catch(ArithmeticException e) {
e.printStackTrace();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
System.out.println("Section 1 is over");
}
}
注意,在try-catch-finally机制中,若有finally,则finally一定被执行。
同时,try-catch-finally每个模块也可能发生异常,可以再其中再套用try-catch-finally,和 if语句 套用类似。
throws
在方法中,有时候对于可能存在异常的语句并不进行处理,而是用throws来声明异常。
对于有throws异常(Checked Exception)的方法,调用时要么处理这些异常(try-catch),要么再次向外部throws,直至到 main 函数为止。而 main 函数会把异常传给 JVM,JVM 遇到异常直接输出异常信息,然后中断程序
比如:
- try… finally 语句中,若 try 过程中出现异常,那么会在 finally 执行结束后退出程序,并打印错误信息。就是这个语句块把错误 throws 给 main 方法,然后 throws 给了 JVM
注意:
- throws 抛出的异常可以是方法实际产生的异常类型,也可以是它的父类
- throws 可以抛出多个异常,异常类型之间用 , 隔开
- 对于编译异常,程序中必须处理,可以用 try-catch 或者是 throws
- 对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
- 在 throws 过程中,如果有 try-catch 捕捉了异常,就算是处理异常
- throws 抛出的类型若是运行时异常,那么它的外部可以不进行 throws(不报编译错误),JVM 会自行处理
例子:
public class Throws {
public static void main(String[] args) {
try {
// 调用该divide方法应该承担异常处理的任务
int result = new Test().divide(3, 1);
System.out.println("the first result is " + result);
}
// 异常处理
catch(ArithmeticException e) {
e.printStackTrace();
}
// 未处理异常,整个程序向外爆发异常
int result = new Test().divide(3, 0);
System.out.println("the second result is " + result);
}
}
class Test {
// ArithmeticException is a RuntimeException, not checked Exception
public int divide(int x, int y) throws ArithmeticException {
int result = x/y;
return result;
}
}
执行结果:
the first result is 3
Exception in thread "main" java.lang.ArithmeticException: / by zero
at courseTest.Test.divide(Throws.java:19)
at courseTest.Throws.main(Throws.java:12)
例子2:
// public 是方法修饰词,代表所有类都可调用该方法
// static 也是方法修饰词,代表该方法是静态方法,可通过类名调用,不可有非静态内容
// MyException 是自定义异常,下一小节会介绍
public static void testException() throws MyException {
throw new MyException("10001", "The reason of myException");
}
public static void main(String[] args) throws MyException {
MyExceptionTest.testException();
// 如果main函数未加throws,需要用try-catch来捕捉处理异常,否则报错
// try {
// MyExceptionTest.testException();
// }
// catch(MyException e) {
// e.printStackTrace();
// }
}
异常相关的继承问题
- 一个方法被覆盖,覆盖它的方法必须抛出相同的异常,或者异常的父类
- 如果父类抛出多个异常,子类可以抛出父类异常的子集,不可以有新的异常
例子:
public class Father {
public void f1() throws ArithmeticException {
}
}
public class Son extends Father {
// 会报错,因为未抛出相同的异常,或者是异常的子类
// public void f1() throws Exception {
//
// }
// 正确
public void f1() throws ArithmeticException {
}
}
异常处理重点:
- try-catch-finally
- 使用throw抛出异常
- 子类声明的异常不能超过父类异常的范围
自定义异常
Exception 是所有异常的父类。继承自 java.lang.Throwable,同时有一个 兄弟类 Error。其中 Error 是系统层面的错误,无需程序处理。程序只需要处理Exception。
自定义异常,需要继承Exception类或其子类。
- 继承自Exception,就变成Checked Exception,编译工具会辅助检查
- 继承自RuntimeException,就变成Unchecked Exception
重点在构造函数
- 调用父类Exception的message构造函数
- 可以定义自己的成员变量,不局限于message
在程序中使用throws主动抛出异常
同时需要注意,在方法头部声明中使用throws抛出异常;在方法内部程序中使用throw抛出异常。
例子:
// 自定义异常类
public class MyException extends Exception{
private String returnCode; // 异常对应的返回码
private String returnMsg; // 异常对应的描述信息
// 无参构造函数
public MyException() {
super(); // 父类的构造函数
}
public MyException(String returnMsg) {
super(returnMsg); // 父类的有参构造函数
this.returnMsg = returnMsg;
}
public MyException(String returnCode, String returnMsg) {
super();
this.returnCode = returnCode;
this.returnMsg = returnMsg;
}
public String getReturnCode() {
return returnCode;
}
public String getReturnMsg() {
return returnMsg;
}
}
测试一下:
public class MyExceptionTest {
// 头部使用throws,代表方法可能抛出异常
public static void testException() throws MyException {
throw new MyException("10001", "The reason of myException");
}
public static void main(String[] args) {
// main未加throws,使用下行语句会报错
// MyExceptionTest.testException();
// 如果main函数未加throws,需要用try-catch来捕捉处理异常,否则报错
// 报错原因是因为 MyException 继承了 Exception,是非RuntimeException异常,也就是Checked Exception
// 表示编译器会辅助检查是否有错
try {
MyExceptionTest.testException();
}
catch(MyException e) {
e.printStackTrace();
System.out.println("returnCode:" + e.getReturnCode());
System.out.println("returnMsg:" + e.getReturnMsg());
}
}
}
输出结果:
courseTest.MyException
at courseTest.MyExceptionTest.testException(MyExceptionTest.java:6)
at courseTest.MyExceptionTest.main(MyExceptionTest.java:18)
returnCode:10001
returnMsg:The reason of myException
例子2:
public class Student {
public int divide(int x, int y) {
return x/y;
}
public static void main(String[] args) throws DivideByMinusException {
Student newton = new Student();
newton.divide2(5, 0);
newton.divide5(5, -2);
}
public int divide2(int x, int y) {
int result;
try {
result = x/y;
System.out.println("result is " + result);
}
catch (ArithmeticException e) {
System.out.println(e.getMessage());
return 0;
}
catch(Exception e) {
e.printStackTrace();
return 0;
}
return result;
}
// ArithmeticException is unchecked exception
public int divide3(int x, int y) throws ArithmeticException {
return x/y;
}
public int divide4(int x, int y) {
// try {
//
// return divide3(x, y);
// }
// catch (ArithmeticException e) {
// e.printStackTrace();
// return 0;
// }
// 调用divide3(其throws异常),不过ArithmeticException是编译器不管的,此处也不会报错
// 如果调用divide5,则需要在头部throws相同的异常,或者是使用上方try-catch
return divide3(x, y);
}
public int divide5(int x, int y) throws DivideByMinusException {
try {
if (y < 0) {
// 将该异常返回给调用方
throw new DivideByMinusException("the divisor is negetive", y);
}
return divide3(x, y);
}
catch(ArithmeticException e) {
e.printStackTrace();
return 0;
}
}
}
输出结果:
/ by zero
Exception in thread "main" courseTest.DivideByMinusException: the divisor is negetive
at courseTest.Student.divide5(Student.java:56)
at courseTest.Student.main(Student.java:12)
关于自定义异常的重点:
- 自定义异常继承自Exception或者RuntimeException
- 需要使用父类的构造函数,可以自定义异常信息
- 使用throw抛出异常
throw 和 throws 的区别
意义 | 位置 | 后面跟的东西 | |
---|---|---|---|
throws | 异常处理的一种方式,抛出异常 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |