java声明异常怎么用,如何善用Java异常

Java的异常算是Java语言的一个特色了。也是在日常编码中会经常使用到的东西。但你真的了解异常吗?

这里有一些关于异常的经典面试题:

Java与异常相关的类结构和主要继承关系是怎样的?

Java7在关于异常的语法上做了什么改进?

什么是运行时异常和声明式异常?它们有什么区别?

什么是“异常丢失(异常覆盖)”问题?

什么是异常链?

什么是返回值覆盖?

编写异常时的一些最佳实践?

如果以上问题的答案你都能了然与胸,那么恭喜你,已经很熟悉Java异常这一块了。

如果一些问题还弄不清楚?没关系,看完这篇文章就可以了。

异常的层次结构

先上图

04ca08d861c08428e3970213619f6f06.png

抛开下面那些异常不谈,我们的关注点可能主要在四个类上:

Throwable

Error

Exception

RuntimeException

其中,因为Error代表“错误”,多为比较严重的错误。如果你了解JVM,应该对OutOfMemoryError和StackOverflowError这两个类比较熟悉。

一般我们在写代码时,可能用的比较多的是Exception类和RuntimeException类。

那到底是继承Exception类好还是继承RuntimeException类好呢?后面我们在“编写异常的最佳实践”小节会讲到。

Java7与异常

Java7对异常做了两个改进。第一个是try-with-resources,第二个是catch多个异常。

try-with-resources

所谓的try-with-resources,是个语法糖。实际上就是自动调用资源的close()函数。和Python里的with语句差不多。

不使用try-with-resources,我们在使用io等资源对象时,通常是这样写的:

String getReadLine() throws IOException{

BufferedReader br = new BufferedReader(fileReader);

try {

return br.readLine();

} finally {

if (br != null) br.close();

}

}

复制代码

使用try-with-recources的写法:

String getReadLine() throws IOException{

try (BufferedReader br = new BufferedReader(fileReader)) {

return br.readLine();

}

}

复制代码

显然,编绎器自动在try-with-resources后面增加了判断对象是否为null,如果不为null,则调用close()函数的的字节码。

只有实现了java.lang.AutoCloseable接口,或者java.io.Closable(实际上继随自java.lang.AutoCloseable)接口的对象,才会自动调用其close()函数。

有点不同的是java.io.Closable要求一实现者保证close函数可以被重复调用。而AutoCloseable的close()函数则不要求是幂等的。具体可以参考Javadoc。

但是,需要注意的是try-with-resources会出现异常覆盖的问题,也就是说catch块抛出的异常可能会被调用close()方法时抛出的异常覆盖掉。我们会在下面的小节讲到异常覆盖。

多异常捕捉

直接上代码:

public static void main(String[] args){

try {

int a = Integer.parseInt(args[0]);

int b = Integer.parseInt(args[1]);

int c = a / b;

System.out.println("result is:" + c);

} catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) {

System.out.println("发生了以上三个异常之一。");

ie.getMessage();

// 捕捉多异常时,异常变量默认有final修饰,

// 所以下面代码有错:

// ie = new ArithmeticException("test");

}

}

复制代码

Suppressed

如果catch块和finally块都抛出了异常怎么办?请看下下小节分析。

运行时异常和声明式异常

所谓运行时异常指的是RuntimeException,你不用去显式的捕捉一个运行时异常,也不用在方法上声明。

反之,如果你的异常只是一个Exception,它就需要显式去捕捉。

示例代码:

void test(){

hasRuntimeException();

try {

hasException();

} catch (Exception e) {

e.printStackTrace();

}

}

void hasException() throws Exception{

throw new Exception("exception");

}

void hasRuntimeException(){

throw new RuntimeException("runtime");

}

复制代码

虽然从异常的结构图我们可以看到,RuntimeException继承自Exception。但Java会“特殊对待”运行时异常。所以如果你的程序里面需要这类异常时,可以继承RuntimeException。

而且如果不是明确要求要把异常交给上层去捕获处理的话,我们建议是优先使用运行时异常,因为它会让你的代码更加简洁。

什么是异常覆盖

正如我们前面提到的,在finally块调用资源的close()方法时,是有可能抛出异常的。与此同时我们可能在catch块抛出了另一个异常。那么catch块抛出的异常就会被finally块的异常“吃掉”。

看看这段代码,调用test()方法会输出什么?

void test(){

try {

overrideException();

} catch (Exception e) {

System.out.println(e.getMessage());

}

}

void overrideException() throws Exception{

try {

throw new Exception("A");

} catch (Exception e) {

throw new Exception("B");

} finally {

throw new Exception("C");

}

}

复制代码

会输出C。可以看到,在catch块的B被吃掉了。

JDK提供了Suppressed的两个方法来解决这个问题:

// 调用test会输出:

// C

// A

void test(){

try {

overrideException();

} catch (Exception e) {

System.out.println(e.getMessage());

Arrays.stream(e.getSuppressed())

.map(Throwable::getMessage)

.forEach(System.out::println);

}

}

void overrideException() throws Exception{

Exception catchException = null;

try {

throw new Exception("A");

} catch (Exception e) {

catchException = e;

} finally {

Exception exception = new Exception("C");

exception.addSuppressed(catchException);

throw exception;

}

}

复制代码

异常链

你可以在抛出一个新异常的时候,使用initCause方法,指出这个异常是由哪个异常导致的,最终形成一条异常链。

详情请查阅公众号之前的关于异常链的文章。

返回值覆盖

跟之前的“异常覆盖”问题类似,finally块会覆盖掉try和catch块的返回值。

所以最佳实践是不要在finaly块使用return!!!

最佳实践?

如果可以,尽量使用RuntimeException

尽量不要在finally块抛出异常或者返回值

尽量使用Java7对异常的新语法

try-catch块可以单独抽到一个方法里面去,让你的代码更简洁 —— 参考《代码整洁之道》第7章

记录异常日志可以结合log和printStackTrace

参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值