Java学习45:抛出异常

纵饮孤独 提交于 2020-02-04 13:39:48

我们在SixthDemo项目中的src目录下创建一个新的包,名为com.sixth.throws_exception,并创建Java文件Demo02.java
在这里插入图片描述

异常的传播
当某个方法抛出了异常时,如果当前方法没有捕获异常,异常就会被抛到上层调用方法,直到遇到某个try … catch被捕获为止:

public class Demo02  {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        process2();
    }

    static void process2() {
        Integer.parseInt(null); // 会抛出NumberFormatException
    }
}

在这里插入图片描述
通过printStackTrace()可以打印出方法的调用栈,例如上图打印的就是我们的报错信息,注意这里是打印出的报错信息,而不是程序自己报错。
printStackTrace()对于调试错误非常有用,上述信息表示:NumberFormatException是在java.lang.Integer.parseInt方法中被抛出的,调用层次从上到下依次是:

  1. main()调用process1();
  2. process1()调用process2();
  3. process2()调用Integer.parseInt(String);
  4. Integer.parseInt(String)调用
  5. Integer.parseInt(String, int)。

查看Integer.java源码可知,抛出异常的方法代码如下:

public static int parseInt(String s, int radix) throws NumberFormatException {
    if (s == null) {
        throw new NumberFormatException("null");
    }
    ...
}

并且,每层调用均给出了源代码的行号,可直接定位。

抛出异常
当发生错误时,例如,用户输入了非法的字符,我们就可以抛出异常。

如何抛出异常?参考Integer.parseInt()方法,抛出异常分两步:

  • 创建某个Exception的实例;
  • 用throw语句抛出。

下面是一个例子:

void process2(String s) {
    if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
    }
}

实际上,绝大部分抛出异常的代码都会合并写成一行:

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就相当于把抛出的异常类型“转换”了:

void process1(String s) {
    try {
        process2();
    } catch (NullPointerException e) {
        throw new IllegalArgumentException();
    }
}

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

当process2()抛出NullPointerException后,被process1()捕获,然后抛出IllegalArgumentException()。

如果在main()中捕获IllegalArgumentException,我们看看打印的异常栈:

public class Demo02 {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

在这里插入图片描述
打印出的异常栈如上图;
这说明新的异常丢失了原始异常信息,我们已经看不到原始异常NullPointerException的信息了。

为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。对上述代码改进如下:

public class Demo02 {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

在这里插入图片描述
运行代码,打印出的异常栈如上图;

注意到Caused by: Xxx,说明捕获的IllegalArgumentException并不是造成问题的根源,根源在于NullPointerException,是在Main.process2()方法抛出的。

在代码中获取原始异常可以使用Throwable.getCause()方法。如果返回null,说明已经是“根异常”了。

有了完整的异常栈的信息,我们才能快速定位并修复代码的问题。

如果我们在try或者catch语句块中抛出异常,finally语句是否会执行?例如:

public class Demo02 {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
        }
    }
}

在这里插入图片描述
上述代码运行结果如图;
第一行打印了catched,说明进入了catch语句块。第二行打印了finally,说明执行了finally语句块。

因此,在catch中抛出异常,不会影响finally的执行。JVM会先执行finally,然后抛出异常。

异常屏蔽
如果在执行finally语句时抛出异常,那么,catch语句的异常还能否继续抛出?例如:

public class Demo02 {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
            throw new IllegalArgumentException();
        }
    }
}

在这里插入图片描述
执行上述代码,结果如图;
这说明finally抛出异常后,原来在catch中准备抛出的异常就“消失”了,因为只能抛出一个异常。没有被抛出的异常称为“被屏蔽”的异常(Suppressed Exception)。

在极少数的情况下,我们需要获知所有的异常。如何保存所有的异常信息?方法是先用origin变量保存原始异常,然后调用Throwable.addSuppressed(),把原始异常添加进来,最后在finally抛出:

public class Demo02 {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}

在这里插入图片描述
当catch和finally都抛出了异常时,虽然catch的异常被屏蔽了,但是,finally抛出的异常仍然包含了它;
通过Throwable.getSuppressed()可以获取所有的Suppressed Exception。

绝大多数情况下,在finally中不要抛出异常。因此,我们通常不需要关心Suppressed Exception。

练习
捕获异常

public class Demo02 {

	public static void main(String[] args) {
		System.out.println(tax(2000, 0.1));
		System.out.println(tax(-200, 0.1));
		System.out.println(tax(2000, -0.1));
	}

	static double tax(int salary, double rate) {
		// TODO: 如果传入的参数为负,则抛出IllegalArgumentException
		return salary * rate;
	}
}

小结
调用printStackTrace()可以打印异常的传播栈,对于调试非常有用;

捕获异常并再次抛出新的异常时,应该持有原始异常信息;

通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。调用方可通过Throwable.getSuppressed()获取所有添加的Suppressed Exception。

练习题答案

public class Demo02 {

    public static void main(String[] args) {
        System.out.println(tax(2000, 0.1));
        System.out.println(tax(-200, 0.1));
        System.out.println(tax(2000, -0.1));
    }

    static double tax(int salary, double rate) {
        // TODO: 如果传入的参数为负,则抛出IllegalArgumentException
        try {
            if (salary<0 | rate<0){
                throw new IllegalArgumentException("参数为负数");
            }
        }finally {
            System.out.println("End");
        }
        return salary * rate;
    }
}

在这里插入图片描述

谢谢观看

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!