常见的异常
- 除以 0
System.out.println(10 / 0);
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: / by zero
- 数组下标越界
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
- 访问 null 对象
public class Test {
public int num = 10;
public static void main(String[] args) {
Test t = null;
System.out.println(t.num);
}
}
// 执行结果
Exception in thread "main" java.lang.NullPointerException
异常体系
主要分为
- 错误(只能程序员自己解决)
- 异常(可以处理异常)
异常又分为
- 运行时异常(非受查异常) :程序依然能运行,运行的时候出现异常
- 编译时异常(受查异常):如果不处理,程序不能运行
防御式编程
错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿. 我们有两种主要的式:
- LBYL: Look Before You Leap. 在操作之前就做充分的检查.
- EAFP: It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理
捕获异常
try{
有可能出现异常的语句 ;
}catch (异常类型 异常对象) {
处理异常
} ...
finally {
异常的出口
}
- try 代码块中放的是可能出现异常的代码.
- catch 代码块中放的是出现异常后的处理行为.
- finally代码块中的代码用于处理善后工作, 会在最后执行.
- 其中 catch 和 finally 都可以根据情况选择加或者不加.
如果不处理异常
public class Work1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}
}
则程序在发生异常后不再往后执行
如果用try-catch处理异常
public class Work1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println("before");
try {
System.out.println(arr[100]);
}catch (ArrayIndexOutOfBoundsException e) {
//打印出异常的调用栈
e.printStackTrace();
}
System.out.println("after");
}
}
处理异常后,程序依然可以往后执行,打印出了after
finally关键字
public class Work1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println("before");
try {
System.out.println(arr[100]);
}catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}finally {
System.out.println("finally 肯定会被执行!");
}
System.out.println("after");
}
}
所以说,不管是否会发生异常,finally里的内容一定会被执行!
作用:用于释放资源,文件打开或关闭等
注意事项:
public static int func() {
try {
return 10;
} finally {
return 20;
}
}
如果finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return
所以不建议在finally中写return语句。
异常处理流程
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
抛出异常
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作。
public static void func(int y){
if(y == 0) {
//抛出异常
throw new ArithmeticException();
}
System.out.println(10/y);
}
声明异常
我们在处理异常的时候, 通常希望知道这段代码中究竟会出现哪些可能的异常.
我们可以使用 throws 关键字, 把可能抛出的异常显式的标注在方法定义的位置. 从而提醒调用者要注意捕获这些异常
//声明异常
public static void func(int y) throws ArithmeticException{
if(y == 0) {
//抛出异常
throw new ArithmeticException();
}
System.out.println(10/y);
}
自定义异常
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常
class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
}
public class Demo {
public static void main(String[] args) {
try {
throw new MyException("我定义的异常");
}catch (MyException e) {
e.printStackTrace();
}
}
}
这样子我们就自己定义了一种异常。
再比如我们实现一个用户登陆功能
- 先编写用户名和密码错误的 自定义异常类
class UserError extends Exception {
public UserError(String message) {
super(message);
}
}
class PasswordError extends Exception {
public PasswordError(String message) {
super(message);
}
}
- 然后模拟登录
public class Test {
private static String userName = "admin";
private static String password = "123456";
public static void login(String userName, String password) throws UserError, PasswordError {
if (!Test.userName.equals(userName)) {
throw new UserError("用户名错误");
}
if (!Test.password.equals(password)) {
throw new PasswordError("密码错误");
}
System.out.println("登陆成功");
}
public static void main(String[] args) {
try {
login("admin", "123");
} catch (UserError userError) {
userError.printStackTrace();
} catch (PasswordError passwordError) {
passwordError.printStackTrace();
}
}
}
如果我们输入的用户名和密码有误,就会处理这样一个自定义异常
注意事项:
- 自定义异常通常会继承自 Exception 或者 RuntimeException
- 继承自 Exception 的异常默认是受查异常
- 继承自 RuntimeException 的异常默认是非受查异常