所谓异常指的就是程序在
运行时
出现错误时通知调用者的一种机制。
运行时指的是程序
已经编译通过
得到 class 文件了,
再由
JVM 执行过程中
出现的错误。而对编译错误而言,在编译过程中如果编译通过那么一定不存在编译错误。
错误在代码中是客观存在的。
因此要让程序出现问题的时候及时告知程序员
有两种主要的方式:
1)LBYL:
Look Before You Leap。即
在操作之前就做充分的检查。检查完上一步之后再做下一步操作,上一步失败就不会执行下一步操作了。以进行一局游戏为例:
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return; }
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return; }
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return; }
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return; }
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return;
}
......
2)EAFP:It's Easier to Ask Forgiveness than Permission。即先操作,遇到问题时再处理。异常的核心思想就是 EAFP 。同样以进行一局游戏为例:
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}......
一、捕捉异常
基本语法如下:
try{
//有可能出现异常的代码;
}catch (异常类型 异常对象) {
//出现异常后的处理行为;
}
finally {
//处理善后工作,会在最后执行;
}
• 其中 catch 和 finally 都可以根据情况选择加或者不加。
• try 中的代码按顺序执行,一旦 try 中的代码出现异常,那么 try 代码块中的程序就不会继续执行,而是交给对应异常种类的 catch 语句来处理,catch 中的代码执行完毕后会继续往下执行。
• catch 可以有多个 。如果多个异常对应的处理方式是完全相同的,则可以在不同异常种类之间用‘ | ’连接放在一个 catch() 中。
• finally 中的语句保证一定会执行到,无论前面是否触发了异常,因此我们可以利用 finally 进行资源释放、关闭文件、多线程解锁等。finally 执行的时机是在方法返回之前,因此在 try 或者 catch 中如果有 return 会在这个 return 之前执行 finally;但是如果 finally 中也存在 return 语句,那么就会执行 finally 中的 return,从而不会执行到 try 中原有的 return。
※关于异常处理方式:
1. 对于比较严重的问题
(
例如和计算钱有关
),
应该让程序直接崩溃,
防止造成更严重的后果;
2. 对于不太严重的问题
(
大多数场景
),
可以记录错误日志
并通过监控报警程序及时通知程序员;
3.对于可能会恢复的问题
(
和网络相关的场景
),
可以尝试进行重试重连。
※关于“调用栈”:
方法之间是存在相互调用关系的,
这种调用关系我们可以用
"
调用栈
"
来描述。
在
JVM
中有一块内存空间称为 "
虚拟机栈"
专门存储方法之间的调用关系。
当代码中出现异常的时候,
我们就可以使用
e.printStackTrace(); (会把数据写到 System.err 文件里,与 System.out 是两个独立的文件,由于二者缓冲区的刷新策略不同,会导致二者之间的执行顺序不一定严格)
的方式查看出现异常代码的调用栈。其中,e 是个形参,对应着 try 代码抛出的异常对象,通过 e 可以获取到很多有关此异常的信息。
※
Exception
类是所有异常类的父类
。
因此可以用这个类型表示
捕捉所有异常
。catch 进行类型匹配的时候
不光会匹配相同类型的异常对象,
也会捕捉目标异常类型的子类
对象。
※使用 try
负责回收资源,举个例子:
try (Scanner sc = new Scanner(System.in)) {
int num = sc.nextInt();
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
将
Scanner
对象在
try
的
( )
中创建,
就能保证在
try
执行完毕后
自动调用
Scanner
的
close
方法。
※ 如果本方法中没有合适的处理异常的方式, 就会 沿着调用栈向上传递 (即向上转型)。如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理 , 程序就会异常终止 ( 和 未使用 try catch 时是一样的 )。
二、抛出异常
除了
Java
内置的类会抛出一些异常之外,程序员
也可以
手动抛出
某个异常。
使用
throw 关键字
完成这个操作。并且
我们在处理异常时
通常希望知道这段代码中究竟会出现哪些可能的异常,
就可以使用
throws 关键字
,
把可能抛出的异常
显式的标注在方法定义的位置
,
从而提醒调用者要注意
捕获这些异常。举个例子:
public static int divide(int x, int y) throws ArithmeticException {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y; }
三、Java异常体系
上图表示了 Java 内置的异常类之间的继承关系。
顶层类 Throwable
派生出两个重要的子类:
Error
和
Exception。其中
Error
指的是
Java
运行时
内部错误和资源耗尽错误
(JVM系统级别)
,
应用程序不抛出此类异常。
这种内部错误一旦出现,除了告知用户并使程序终止之外
再无能无力,
这种情况很少出现。
Exception
是程序员所使用的异常类的父类。其中 Exception
有一个子类称为
RuntimeException,
这个类又派生出很多我们常见的异常类,比如:NullPointerException,
IndexOutOfBoundsException
等。
Java语言规范将派生于
Error
类或
RuntimeException
类的所有异常称为
非受查异常
,
所有的其他异常称为
受查异常
。
受查异常
必须显式进行处理
(
使用
try catch 包裹
起来;
在
方法上加上异常说明
,
相当于将处理动作
交给上级调用者
)
,非受查异常可不必。
四、自定义异常类
举个用户登录功能的例子:
//Main.java
package Exception;
import java.util.Scanner;
public class Main {
private static String userName = "admin";
private static String userPassword = "123456";
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String name = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
try{
login(name,password);
}catch (NameException nameException){
nameException.printStackTrace();
}catch (PasswordException passwordException){
passwordException.printStackTrace();
}
}
public static void login(String name, String password) throws NameException, PasswordException {
if(!userName.equals(name))
throw new NameException("用户名输入有误");
if(!userPassword.equals(password))
throw new PasswordException("密码输入有误");
System.out.println("登陆成功");
}
}
//NameException.java
package Exception;
public class NameException extends Exception {
public NameException(String message) {
super(message);
}
}
//PasswordException.java
package Exception;
public class PasswordException extends Exception {
public PasswordException(String message) {
super(message);
}
}
• 自定义异常通常会继承自 Exception
或者
RuntimeException。
继承自 Exception
的异常默认是受查异常;
继承自 RuntimeException
的异常默认是非受查异常。