这是我参与更文挑战的第26天,活动详情查看: 更文挑战
前言
今天,来谈谈 程序编写中的 Exception 和 Error ,这些错误在我们开始写代码之后就一直如影随形的纠缠着我们,java 语言中提供了相对完善的异常处理机制,接下来,我们把异常和错误一次性搞明白。
一、异常概述
1.1 入门小故事
我们通过一个故事来看看几种情况,分别来了解异常和错误的概念。
假设在一个风和日丽的天气,我打算去理发,于是我准备去找托尼老师……
- 1、我走到托尼老师的店门口,发现理发店门上贴着 “ 本店倒闭,暂停营业 ”。那么这对我来说我一点办法都没有,我的理发计划只能泡汤了。(这是严重的错误)
- 2、假设理发店的托尼老师人在隔壁花店撩小妹妹,我去把他叫了回了,这样我的理发计划可以继续实现了。(这种我能在事发前处理的事,对应编译期异常。)
- 2、假设托尼老师给我开始理发了,我让他来个碎发,结果他给我来了碗 3 mm 的寸头。(这种我在事发前不能预料的,对应运行时异常。)
1.2 基础概念
看完上面的小故事,我们也大概知道异常和错误的区别了,基础概念如下:
- Error: 对应情况 1,一般是jvm发生错误,stackOverlowErrot,outOfMemeryError,一般程序写的有问题,确实内存不够用。
- Exception:
- 编译期异常:对应情况2,这块我们应该进行异常处理的,否则程序无法通过编译。
- 运行时异常:对应情况3,RuntimeException 及其子类,一般是程序的逻辑有问题,不推荐进行异常处理,而应该修改代码逻辑。
1.3 异常分类
异常和错误它们都继承 Throwable 类,它是 Java 语言中所有错误或异常的超类,只有当对象是此类或其子类时,才能通过 JVM 虚拟机或 Java throw 语句抛出来。
【注解】:
-
- 粉红色的是受检查的异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.受检查的异常必须在编译时被捕捉处理,命名为 Checked Exception 是因为Java编译器要进行检查,Java虚拟机也要进行检查,以确保这个规则得到遵守.
-
- 绿色的异常是运行时异常(runtime exceptions),需要程序员自己分析代码决定是否捕获和处理,比如 空指针,被0除…
-
- 而声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或者不可能捕捉,将导致应用程序中断,Error不需要捕捉。
二、异常处理
2.1 处理方式
前面说过 Throwable 的分为 error 和 Exception 两大类,在它们发生时,应该怎么处理它们呢?
- 1、JVM 的默认处理 : 我们这种情况很常见,jvm会先打印出错误的线程名称和异常的类名,异常的原因,打印出异常发生的位置,然后终止程序。
- 2、try_catch_finally 处理,进行异常捕获
- 3、throw 把问题抛给别人
【说明】:
默认的处理方式说白了就是不处理,抛出异常信息,然后直接终止程序,不可以,通常需要用 try – catch 来处理,如果实在处理不了,就用throw 来抛出异常 交给它的上一级来处理,程序便不往下执行了,区别就i是try-catch处理完会进行执行后面的程序,而throw 则不会执行。
2.2 处理流程
try_catch_finally 标准格式:
try{
可能发生错误的代码
}
catch(异常类名 异常对象名){
对异常处理
}
finally{
释放资源
}
复制代码
标准流程:
-
先执行try语句块里面的代码
-
如果没有发生错误,执行finally语句块里面的代码,执行后面的代码
-
如果发生了错误,JVN会把异常封装成对象,然后和catch语句进行匹配,执行对应的catch语句,执行finally语句块里面的代码,执行后面的代码
-
先catch 小异常,在catch 大异常,注意书写顺序
2.3 知识扩展
下面开始对一些实践中的用例进行分析
第一个:
try {
// 业务代码
// …
Thread.sleep(1000L);
} catch (Exception e) {
// Ignore it
}
复制代码
这段程序虽然短,但是违背了异常处理的两个原则
- 1、尽量不要捕获类似Exception 这样的通用异常,而应该捕获具体异常,例如此块超时抛出的 InterruptedException,这样保证程序不会捕获到我们不希望捕获的异常。
- 2、不要忽略异常,在catch 里面需要抛出异常 或者把异常输出到日志然后处理,否则,没人知道异常情况,可能会带来不可理解的错误
第二个:
try {
// 业务代码
// …
} catch (IOException e) {
e.printStackTrace();
}
复制代码
这块代码在是开发环境是没有问题的,但是 printStackTrace 不是个好的输出方式,如果是分布式系统,就得去找输入信息输出到哪里去了,因此,还是需要记录到日志中。
三、面试问题
3.1 final,finally,finalize的区别?
-
final: 最终的
-
修饰类:该类不能被继承
-
修饰方法:该方法不能被重写
-
修饰变量:自定义常量
-
-
finally:
-
finally子句用于释放资源的,放在finally中代码一定会被执行(执行finally语句之前,JVM退出)
-
finalize:
- Object类中的方法。用于垃圾回收
3.2 throw和throws的区别
throw:
-
在方法体中,后面跟的是异常对象名,并且只能抛出一个异常
-
表示抛出异常,由方法体内的语句来处理
-
throw抛出的是一个异常对象,说明这里肯定有一个异常产生了
throw new Exception();
throws:
-
在方法声明上,后面跟的是异常的类名,可以是多个异常类名,用逗号隔开
-
表示可能抛出异常,异常由该方法的调用者来处理
-
throws是声明方法有异常,是一种可能性,这个异常并不一定会产生
3.3 如果在catch里面有return,请问finally还执行吗?如果执行,在return前还是后?
答:finally 的执行应该在return 的前面。
下面通过一个例子来看看:
int init(){
int a=10;
System.out.println(a);
try{
System.out.println(a/0);
a=20;
System.out.println("发生异常,此时a="+a);
}catch (ArithmeticException e){
a=30;
System.out.println("我处理异常,此时a="+a);
return a;
}finally {
a=40;
System.out.println("我是finally,此时a="+a);
}
System.out.println("最终的a值:"+a);
return a;
}
复制代码
@Override
public void run(String... args) throws Exception {
System.out.println("最终的值:"+this.init());
}
int init(){
int a=10;
System.out.println(a);
try{
System.out.println(a/0);
a=20;
System.out.println("发生异常,此时a="+a);
}catch (ArithmeticException e){
a=30;
System.out.println("我处理异常,此时a="+a);
return a;
/*
* return a在程序执行到这一步的时候,这里不是return a而是return 30;这个返回路径就形成了。
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
* 再次回到以前的返回路径,继续走return 30;
*/
}finally {
a=40;
System.out.println("我是finally,此时a="+a);
}
return a;
}
复制代码
我处理异常,此时a=30
我是finally,此时a=40
最终的值:30
复制代码
可以发现,此时 a的值为30,说明,catch 中 的return 的时候,会继续执行finally 的语句,并且在return 前执行
参考资料:
- 《Java 核心技术36讲》