目录
1.基础概念
不同于编译出错,这里的异常指的是运行时异常,指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误,如我们之前常见的空指针异常、除0异常、数组越界异常等
如: 空指针异常
public static void main(String[] args) {
String str = null;
System.out.println(str.equals("1"));
}
执行结果:
NullPointerException 即为空指针异常
JDK中的内置异常类均在java.lang包下
java.lang.Throwable类是Java中所有异常或者错误的超类
2.避免异常的两种方式
LBYL:
Look Before You Leap. 在操作之前就做充分的检查.
EAFP:
It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理.
在java中主要是用EAFP方法,先做,如果有异常,我们再对异常处理
通常是用try - catch - finally进行捕获异常
如果某方法可能存在异常,但我不想在这里处理,我们就可以使用throws抛出异常给调用它的上一级,上一级在调用该方法时使用try - catch - finally 捕获异常
3.异常的基本语法:
try - catch -finally捕获异常
try{
// 有可能出现异常的代码块
}catch ((异常类 异常对象)Exception e){
// 捕获到该异常后执行的代码
}finally{
// 无论是否有异常发生都会执行的代码,善后处理
}
}
注意:
(1)catch语句可有0 - n个,即可以不写catch语句,也可以写多个,如果可能抛出的异常不止一个,就可以写多个catch语句
(2)finally语句可有0或1个,无论是否捕获到异常,均会执行finally语句,finally语句通常用于资源的关闭释放等操作
(3)catch语句中,catch的小括号里即是写捕获到的异常类的异常对象,如上述代码中的 Exception e,就是Exception 异常类的对象e
(4)这里要说明,Exception类是所有异常类的共同父类,所以当可能有多个异常或者具体是说明异常自己也不清楚的时候,可以用Exception类代替,但并不推荐这种写法,因为这样写不便于排查异常原因,所以通常还是选择用多个catch语句捕获异常
异常处理流程:
程序先执行 try 中的代码
如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
4.异常的体系:
(1)Error类是程序内部错误,程序员无法捕获处理,所以 我们主要关注Exception类及其子类
(2)异常按照是否必须显式处理又分为非受查异常和受查异常
非受查异常:
不强制要求处理,只有Error类和RuntimeException类及其子类是非受查异常,其他都是受查异常
受查异常:
必须使用try -catch捕获异常或者throws抛出异常,否则会编译报错!!!
5.关于异常的调用链
如果一个方法产生了异常,但并没有对该异常进行处理,则会将异常抛出给其方法调用者,也就是说,只要异常没有遇到处理方法,就会一直抛给调用它的上一级,直至被处理或者最后扔给JVM
在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.
6.关键字 throw 和throws
throws:用在方法声明上,明确表示该方法会产生该异常,但方法本身并没有对异常进行处理,所以该方法的异常会抛给调用它的上一级,所以 throws 关键字, 把可能抛出的异常显式的标注在方法定义的位置. 从而提醒调用者要注意捕获这些异常.
throw:用在方法内部,表示人为抛出异常,通常和自定义异常结合使用,在抛出异常后,该方法就会结束
如下面这段代码块,就是throw 和 throws 的用法
// throws用于方法声明,提醒调用者注意对这一异常捕获处理
public static int divide(int x, int y) throws ArithmeticException {
if (y == 0) {
// throw用于方法内部,人为抛出该异常
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
7.自定义异常类
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常.
例如可能我们会用到具体的登录异常,密码错误异常等,这些都需要我们自己去定义
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常
下面以登录异常为例,我们自己创建两个异常类
import java.util.Scanner;
//自定义异常类
//以用户登录为例
public class Login {
private static String name = "阿衡";
private static String password = "123456";
public static void main(String[] args) {
try {
login();
System.out.println("登录成功!");
} catch (nameException e) {
System.out.println("用户名错误!");
// 打印错误堆栈信息
e.printStackTrace();
}
}
// 因为nameException继承自受查异常,所以这里在抛出该异常时,方法声明必须throws该异常交至上一级try-catch,否则会编译报错
public static void login() throws nameException {
System.out.println("请输入您的用户名:");
Scanner scanner = new Scanner(System.in);
String name1 = scanner.next();
System.out.println("请输入用户密码:");
String password1 = scanner.next();
// 如果输入的用户名和预定的不一致,则抛出用户名错误异常
if (!name1.equals(name)) {
throw new nameException("用户名错误!");
}
// 如果密码不一致,则抛出密码错误异常
if (!password1.equals(password)) {
throw new passwordException("密码错误!");
}
}
}
//用户名错误异常
//受查异常,必须显示使用try-catch或throws抛出
class nameException extends Exception {
public nameException(String msg) {
super(msg);
}
}
//密码错误异常
//非受查异常,可以只用于抛出而不使用try-catch或throws处理
class passwordException extends RuntimeException {
public passwordException(String msg) {
super(msg);
}
}
值得注意的是,密码错误异常继承自非受查异常,所以我们可以不在login方法中用throws显式抛给上一级,在main中也没有显式的用catch语句捕获处理
但是,对于受查异常,我们看到,如果不处理会编译报错
最后,综上,我们来个重点小总结:
(1)java用try-catch-finally捕获异常进行处理
(2)throw关键字用于抛出异常
(3)throws关键字则是声明有该异常且我未处理
(4)对于本方法的异常没有处理的或者直接使用throw的我们都会抛给上一级让上一级捕获处理异常
(5)同时,我们可以自定义异常类
(6)最后,就是注意区分非受查异常(Runtimexception及其子类)和受查异常