Java异常处理机制
三种类型的异常
检查性异常
最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。
例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单的忽略
运行时异常
运行时异常是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略
例如递归调用,A方法中调用B方法,B方法中调用A方法
错误
错误不是异常,而是脱离程序员控制的问题,错误在代码中通常被忽略
例如当栈溢出时,一个错误就发生了,它们在编译也检查不到
异常类体系结构
错误:Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。错误不能被程序员通过代码处理,Error很少出现。还有发生在虚拟机试图执行应用时如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心,这些异常是有程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
在Exception分支中有一个重要的子类RuntimeException(运行时异常)
-
ArrayIndexOutOfBoundsException(数组下标越界)
-
NullPointerException(空指针异常)
-
ArithmeticException(算术异常)
-
MissingResourceException(丢失资源)
-
ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
Error和Exception的区别: Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
抛出&捕获异常
异常处理的五个关键字:try、catch、finally、throw、throws
【try...catch...finally】
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int a = input.nextInt();
int b = input.nextInt();
try {
calculate(a, b);
} catch (Exception e) {
e.printStackTrace();
} finally {
input.close();
}
}
public static void calculate(int num1, int num2) {
System.out.println(num1/num2);
}
}
执行结果:
8
0
java.lang.ArithmeticException: / by zero
at com.exception.demo01.Test.calculate(Test.java:19)
at com.exception.demo01.Test.main(Test.java:11)
-
try块中放可能发生异常的代码。
如果执行完try且不发生异常,则接着去执行finally块和finally后面的代码(如果有的话)。
如果执行完try发生异常,则尝试去匹配catch块 -
每一个catch块用于捕获并处理一个特定的异常,或者这异常类型的子类。
catch后面的括号定义了异常类型和异常参数。如果异常与之匹配且是最先匹配到的,则虚拟机 将使用这个catch块来处理异常。
如果当前try块中发生的异常在后续的所有catch中都没捕获到,则先去执行finally -
finally块通常是可选的。 无论异常是否发生,异常是否匹配被处理,finally都会执行。
finally主要做一些清理工作,如流的关闭,数据库连接的关闭等。
一个try至少要有一个catch块,否则, 至少要有1个finally块。但是finally不是用来处理异常的,finally不会捕获异常
【try...catch...catch...finally】catch可以存在多个,但捕获的类型大小要从小到大
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int a = input.nextInt();
int b = input.nextInt();
try {
calculate(a, b);
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
} catch (Exception f) {
System.out.println("Exception");
} catch (Throwable g) {
System.out.println("Throwable");
// 类型:ArithmeticException<Exception<Throwable
}finally {
input.close();
}
}
public static void calculate(int num1, int num2) {
System.out.println(num1/num2);
}
}
throw关键字
可以通过throw语句手动显式的抛出一个异常。throw语句的后面必须是一个异常对象。
throw 语句必须写在方法中,执行throw 语句的地方就是一个异常抛出点
public class Test02 {
public static void main(String[] args) {
new Test02().test(5, 0);
}
public void test(int a, int b) {
if (b == 0){
throw new ArithmeticException();// 主动抛出异常,一般在方法中使用
}
}
}
执行结果:
Exception in thread "main" java.lang.ArithmeticException
at com.exception.demo01.Test02.test(Test02.java:9)
at com.exception.demo01.Test02.main(Test02.java:5)
throws关键字
throws关键字主要用于方法声明上,指的是当方法之中出现异常后交由被调用处处理
调用了throws声明的方法之后,那么不管操作是否出现异常,都必须使用try...catch语句进行异常处理。 主方法使用throws后,那么这个异常就将交给JVM进行处理,而后结束程序调用。
public class Test03 {
public static void main(String[] args) {
try {
new Test03().division(3, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
public void division(int a, int b) throws Exception {
System.out.println(a / b);
}
}
执行结果:
java.lang.ArithmeticException: / by zero
at com.exception.demo01.Test03.division(Test03.java:13)
at com.exception.demo01.Test03.main(Test03.java:7)
throw 和 throws的区别:
- throw: 指的是在方法之中人为抛出一个异常类对象,这个对象可以是自己实例化,或者是已经存在的。
- throws: 指的是在方法的声明上使用,表示此方法在调用时必须处理异常(使用try...catch语句)。
自定义异常
自定义异常类,则继承Exception类即可
自定义异常类步骤
- 创建一个类继承异常父类Exception
- 在具体的实现方法首部抛出异常类(自己创建的那个类),throws的运用
- 在具体的实现方法的内部抛出异常信息,throw的运用
自定义的异常应该总是包含如下的任意构造函数
- 一个无参构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数。
- 一个带有String参数和Throwable参数,并都传递给父类构造函数
- 一个带有Throwable 参数的构造函数,并传递给父类的构造函数
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(Throwable cause) {
super(cause);
}
【演示】
public class MyException extends Exception{
private String name;
public MyException(String a) {
this.name = a;
}
/*
toString:异常打印信息
*/
@Override
public String toString() {
return "MyException{" + "name='" + name + '\'' + '}';
}
}
public class Test {
public void test(String a) throws MyException {
if (a == "我不是张三"){
throw new MyException(a);// 抛出
}
}
public static void main(String[] args) {
try {
new Test().test("我不是张三");
} catch (MyException e) {
System.out.println(e);
}
}
}
执行结果:
MyException{name='我不是张三'}
测试类中异常方法的第二种写法
public class Test02 {
public void test(String a) {
if (a == "我不是张三"){
try {
throw new MyException(a);
} catch (MyException e) {
System.out.println(e);
}
}
}
public static void main(String[] args) {
new Test02().test("我不是张三");
}
}
总结
实际应用中的经验总结
- 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
- 在多重catch块后面,可以加一个catch (Exception)来处理可能会被遗漏的异常对于不确定的代码,也可以加上 try-catch,处理潜在的异常
- 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出具体如何处理异常,要根据不同的业务需求和异常类型去决定
- 尽量添加finally语句块去释放占用的资源