Java异常体系如何熟练运用?

1.异常初识

  1. 运行时异常(非受查异常)
    算数异常,数组越界异常,空指针异常。都是在程序运行的过程中发生的异常
  2. 编译时异常(受查异常)

1.处以0

System.out.println(1/0)

Exception in thread "main" java.lang.ArithmeticException: / by zero

2.数组下标越界

		int[] arr = new int[10];
        System.out.println(arr[10]);

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10

3. 访问 null 对象

private static int num = 10;
    public static void main(String[] args) {
        test t;
        t=null;
        System.out.println(t.num);
    }

Exception in thread "main" java.lang.NullPointerException

4. 异常的好处

1.LBYL【Look Before Your Leap:在操作之前就做充分的检查】

boolean ret = false;
ret = 登陆游戏();
if (!ret) {
	处理登陆游戏错误;
	return;
}
ret = 开始匹配();
if (!ret) {
	处理匹配错误;
	return;
}
ret = 游戏确认();
if (!ret) {
	处理游戏确认错误;
	return;
}
ret = 选择英雄();
if (!ret) {
	处理选择英雄错误;
	return;
}
ret = 载入游戏画面();
if (!ret) {
	处理载入游戏错误;
	return;
}
......

EAFP【It’s Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到 问题再处理】

try {
	登陆游戏();
	开始匹配();
	游戏确认();
	选择英雄();
	载入游戏画面();
	...
} catch (登陆游戏异常) {
	处理登陆游戏异常;
} catch (开始匹配异常) {
	处理开始匹配异常;
} catch (游戏确认异常) {
	处理游戏确认异常;
} catch (选择英雄异常) {
	处理选择英雄异常;
} catch (载入游戏画面异常) {
	处理载入游戏画面异常;
} ......

对比两种不同风格的代码, 我们可以发现
使用第一种方式, 正常流程和错误处理流程代码混在一起, 代码整体显的比较 混乱.
第二种方式正常流程和错误流程是分离开的, 更容易理解代码.

2. 异常的基本用法

try{
	有可能出现异常的语句;
}catch(异常类型 异常对象){
	处理
}finally{
	处理
}
  • try 代码块中放的是可能出现异常的代码.
  • catch 代码块中放的是出现异常后的处理行为.
  • finally 代码块中的代码用于处理善后工作, 会在最后执行. 其中 catch 和 finally 都可以根据情况选择加或者不加

1.不处理异常

	int[] arr = {1, 2, 3};
	System.out.println("before");
	System.out.println(arr[100]);
	System.out.println("after");
	
	before
	Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

我们发现一旦出现异常, 程序就终止了. after 没有正确输出

2.使用 try catch后的程序执行过程

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();// 打印异常程序调用栈
        }
        System.out.println("after try catch");
    }

before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
	at test.main(test.java:8)

我们发现, 一旦 try 中出现异常, 那么 try 代码块中的程序就不会继续执行, 而是交给 catch 中的代码来执行. catch 执 行完毕会继续往下执行.

关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定

  1. 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
  2. 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
    对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.

关于调用栈

方法之间是存在相互调用关系的, 这种调用关系我们可以用 “调用栈” 来描述. 在 JVM 中有一块内存空间称为 “虚 拟机栈” 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的 方式查看出现异常代码的调用栈.

3. catch 只能处理对应种类的异常

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        }catch (NullPointerException e){
            e.printStackTrace();// 打印异常程序调用栈
        }
        System.out.println("after try catch");
    }

before
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at test.main(test.java:8)

此处的 catch 只能捕获 空指针 异常而不能捕获 数组越界 异常,因为异常类型不匹配

4. catch 可以有多个

   public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        }catch (NullPointerException e){
            e.printStackTrace();// 打印异常程序调用栈
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }

before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100
	at bit.test.main(test.java:8)

一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块. 如果多个异常的处理方式是完全相同, 也可以写成这样:

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        }catch (NullPointerException | ArrayIndexOutOfBoundsException e){
            e.printStackTrace();// 打印异常程序调用栈
        }
        System.out.println("after try catch");
    }

before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100

5. 也可以用一个 catch 捕获所有异常(不推荐)

    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try{
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        }catch (Exception e){
            e.printStackTrace();// 打印异常程序调用栈
        }
        System.out.println("after try catch");
    }

before
after try catch
java.lang.ArrayIndexOutOfBoundsException: 100

异常范围太广,不容易定位问题

由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常

6. finally 表示最后的善后工作, 例如释放资源

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        try{
            int number = scanner.nextInt();
        }catch (Exception e){
            e.printStackTrace();// 打印异常程序调用栈
        }finally {
            scanner.close();
            System.out.println("scanner closed");
        }
    }

1
scanner closed

finally 的执行不需要条件,无论有无异常都会执行;catch 只能发生相应的异常才能执行

7. 使用 try 负责回收资源

刚才的代码可以有一种等价写法, 将 Scanner 对象在 try 的 ( ) 中创建, 就能保证在 try 执行完毕后自动调用 Scanner 的 close 方法.

    public static void main(String[] args) {
        try(Scanner scanner = new Scanner(System.in)){
            int number = scanner.nextInt();
        }catch (Exception e){
            e.printStackTrace();// 打印异常程序调用栈
        }
    }

8. 如果本方法中没有合适的方法调用栈就会沿着调用栈向上传递

  private static void func(){
        int[] arr = {1,2,3};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) {
        try{
            func();
        }catch (ArrayIndexOutOfBoundsException e){
            e.printStackTrace();
        }
        System.out.println("after catch");
    }

java.lang.ArrayIndexOutOfBoundsException: 100
	test.func(test.java:6)
	test.main(test.java:10)
after catch

9. 异常处理流程

  1. 首先执行 try 之中的代码
  2. 如果 try 中的代码出现异常, 就结束 try 中的代码, 看和 catch 是否匹配
  3. 如果找到 catch 匹配的异常类型, 就执行 catch 之中的代码
  4. 如果没有找到 catch 之中的代码就会将异常交给上层调用者来处理
  5. 无论是否匹配到这个异常类型, 都会执行在该方法结束之前执行 finally 的方法
  6. 如果上层调用者也没有这个异常的处理方法就会继续向上传递
  7. 一直到 main 函数也没有合适的代码处理异常, 就会交给 JVM 来处理, 此时程序就会终止

10. 抛出异常【throw】

   public static int divide(int x, int y) {
        if (y == 0) {
            throw new ArithmeticException("0 作为被除数");
        }
        return x / y;
    }

    public static void main(String[] args) {
        System.out.println(divide(10,0));
        System.out.println(divide(10,5));
    }

Exception in thread "main" java.lang.ArithmeticException: 0 作为被除数

抛出自己的异常类

class MyException extends RuntimeException{
    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func(){
        int a=10;
        if (a==10){
            throw new MyException();
        }
    }
    public static void main(String[] args) {
        func();
    }
}

Exception in thread "main" bit.MyException

由于继承的 RuntimeException,所以是运行时异常,也叫非受查异常

class MyException extends Exception{
    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func() throws MyException {
        int a=10;
        if (a==10){
            throw new MyException("异常捕获的信息");
        }
    }
    public static void main(String[] args) throws MyException {
        func();
    }
}

由于继承的是 Exception, 所以是编译时异常,受查异常

受查异常非受查异常
ExceptionRunTimeException

11. 异常说明

我们在处理异常的时候,通常希望看到可能抛出什么异常。可以使用 throws 把可能抛出的异常显示的标注在方法定义的位置,来提醒调用者要捕获这些异常

    public static int divide(int x, int y) throws ArithmeticException{
        if (y == 0) {
            throw new ArithmeticException("0 作为被除数");
        }
        return x / y;
    }

    public static void main(String[] args) {
        System.out.println(divide(10,0));
        System.out.println(divide(10,5));
    }

Exception in thread "main" java.lang.ArithmeticException: 0 作为被除数

12. 关于 finally 的注意事项

finally 中的代码一定会执行到,这或许会带来一些误解和麻烦

    private static int func(){
        try{
            return 0;
        }finally {
            return 1;
        }
    }
    public static void main(String[] args) {
        System.out.println(func());
    }

1

finally 执行的时机是在 方法返回 之前【如果 try 或者 catch 中有 return ,会在这个 return 之前执行 finally】,如果 finally 也有 return 则执行 finally 中的 return 而不会执行 try 或 catch 中的 return

3. Java异常体系

在这里插入图片描述

  • 顶层 throwable 派生出两个重要自类, ErrorException
  • 其中 Error 是指 Java 内部错和资源耗尽错误,应用程序不抛出此类异常,这种错误很少见,一但出现这种错误除了告知用户并使程序终止以外别无它力
  • Exception 是所有异常的父类
  • Exception 有一个子类 RuntimeException 衍生出许多的我们常见的异常类
  1. Java 语法规范将派生于 ErrorRunTimeException 类的所有异常称为 非受查异常,所有的其它异常类成为 受查异常
  2. 如果一个类继承了受查异常, 则必须对它显示进行处理

在这里插入图片描述
由于 func 抛出了 Exception 这个受查异常,所以在提醒方法调用者可能会把抛出异常,需要手动进行处理,因此需要在调用它的地方 main 函数中进行手动添加throws Exception处理,否则就会继续上交给 JVM 进行处理

处理方法分为两种

1. 使用 try catch 包裹起来

  private static void func() throws Exception{
        int[] arr = {1,2,3};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) {
        try {
            func();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2. 在方法上加上异常说明, 相当于将处理动作交给上级调用者

    private static void func() throws Exception{
        int[] arr = {1,2,3};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) throws Exception {
        func();
    }

option+enter 进行快速修补代码

4. 自定义异常类

Java 虽然内置了丰富的异常类,但是我们实际应用中会对异常累进行一些扩展。创建符合我们的异常类。

throw 的非受查异常没有参数

class MyException extends RuntimeException{
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func(){
        int a=10;
        if (a==10){
            throw new MyException();
        }
    }
    public static void main(String[] args) {
        func();
    }
}

Exception in thread "main" MyException

throw 的非受查异常异常有参数

class MyException extends RuntimeException{
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func(){
        int a=10;
        if (a==10){
            throw new MyException("haha");
        }
    }
    public static void main(String[] args) {
        func();
    }
}

Exception in thread "main" MyException: haha

throw 的受查异常使用 throws 层层提醒调用者

class MyException extends Exception{
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func() throws MyException {
        int a=10;
        if (a==10){
            throw new MyException("haha");
        }
    }
    public static void main(String[] args) throws MyException {
        func();
    }
}

Exception in thread "main" MyException: haha

throw 的受查异常使用 try catch 直接捕获完毕不用提交给调用者

class MyException extends Exception{
    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}
public class test {
    private static void func(){
        int a=10;
        if (a==10){
            try {
                throw new MyException("haha");
            } catch (MyException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        func();
    }
}

MyException: haha
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值