本文主要介绍了Exception的一些用法以及相应的注意点,并中i但介绍了三种try语句的用法,同时还增加了一些对于Exception的扩展,让大家在使用Exception的时候也能体会到Java语言为什么要设计Exception
文章目录
Exception和Error
Exception 和 Error 都是继承了 Throwable 类
,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
- Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
- Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。
所以我们应该重点关注的是Exception,本文也主要围绕Exception展开。
Checked Exception和Unchecked Exception
Checked Exception
必须要求用语句进行处理,比如用try-catch
包住或者是用throws
抛出异常。Unchecked Exception
则不要求必须进行处理。Unchecked Exception继承自RuntimeException。需要注意的是Error 和RuntimeException都是Unchecked Exception的父类。但我们一般使用并重点关注的是RuntimeException,Error因为无法控制,也无法在程序中被恢复,也不重点关注。Unchecked Exception就是所谓的运行时异常,类似NullPointerException、ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,不要求在写代码的时候强制要求进行捕获或者抛出。
下图为我们需要关注的一些异常,引用自极客时间专栏:《Java核心技术面试精讲》
点此链接购买课程
Unchecked Exception
异常的传递
A方法调用B方法,B方法调用C方法,在执行C方法的时候遇到了异常,则会将异常往上抛,什么叫做往上抛?就是向调用C方法的上一级B方法抛,B方法接到这个异常,若这个异常能被catch住,也就说是catch里面的实例必须是这个异常的实例或者异常的父类的实例,异常被catch住以后就会停止往上抛异常,若没有catch住就会继续向A方法抛异常。若B方法catch住了异常,然后就会执行B方法未执行完的代码,然后B方法执行完以后就会出栈,然后执行A方法。C方法未执行完毕的代码不会再继续执行。
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
makeCall();
}
private static void makeCall() throws ClassNotFoundException {
// 检查异常需要强制处理,需要明确的throws或者catch
System.out.println("调用开始");
call2Exception();
System.out.println("调用结束");
}
private static void call2Exception() throws ClassNotFoundException {
System.out.println("Caller1.call2Exception开始");
try {
call3Exception();
} catch (ClassNotFoundException ex) {
System.out.println("got exception in Caller1: " + ex.getMessage());
}
System.out.println("Caller1.call2Exception结束");
}
private static void call3Exception() throws ClassNotFoundException {
System.out.println("Caller2.call3Exception开始");
callThrowException();
System.out.println("Caller2.call3Exception结束");
}
private static void callThrowException() throws ClassNotFoundException {
System.out.println("Caller3.callThrowException开始");
Class.forName("com.neverland.Rabbit");
System.out.println("Caller3.callThrowException结束");
}
}
执行结果如下图所示
若不在call2Exception()方法中将call3的抛出的异常catch住,异常就会一直往上抛,直至该线程停止。
需要注意的是,虽然我们能使用异常进行程序跳转,但是在程序设计的时候千万不要这么考虑。首先,异常本身就代表程序出了故障,用故障来实现正常的功能是不应该被考虑的,其次,异常的创建和处理是很耗费资源的,程序设计中如果用Exception来做跳转,这样的程序设计是有问题的。
接口中定义的异常
抽象方法中声明抛出异常是接口方法签名的一部分
。也就是说,在实现该抽象方法的时候,不能超出抽象方法中定义的异常类型。实现方法可以不抛出异常,但若是要抛出异常,实现方法中抛出的异常必须是抽象方法中定义的异常或者是其子类。
自定义异常
自定义异常模板
public class MyException extends Exception {
public MyException() {
}
//传入参数:异常的信息
public MyException(String message) {
super(message);
}
//传入参数:异常的信息,造成异常的异常
public MyException(String message, Throwable cause) {
super(message, cause);
}
//传入参数:造成异常的异常
public MyException(Throwable cause) {
super(cause);
}
}
使用举例
MyException
public class MyException extends Exception {
public MyException() {
System.out.println("执行MyException()构造方法");
}
//传入参数:异常的信息
public MyException(String message) {
super(message);
System.out.println("执行MyException(String message)构造方法");
}
//传入参数:异常的信息,造成异常的异常
public MyException(String message, Throwable cause) {
super(message, cause);
System.out.println("执行MyException(String message, Throwable cause)构造方法");
}
//传入参数:造成异常的异常
public MyException(Throwable cause) {
super(cause);
System.out.println("执行MyException(Throwable cause)构造方法");
}
}
public class Main {
public static void main(String[] args) {
try{
testMyException();
} catch (MyException e) {
e.printStackTrace();
}
}
public static void testMyException() throws MyException {
throw new MyException("这是我的自定义异常");
}
}
打印结果:
try-catch语句
try-catch-finally
- finally里面的语句无论如何都是会被执行的,即便catch中已经return 了
- 若catch中已经return了,可以认为finally语句会在catch return以后,后面的方法执行以前,再进行执行
- 无论是因为catch中的return结束还是因为异常结束,finally语句都会执行
- finally里面最好不要有return语句,finally里面的return语句会替换掉try或者是catch中return的值
- 在finally里面给catch中的return的变量赋值是没有用的。比如catch中return了一个int类型的 变量len,然后在finally语句里面将len的值+1,这是没有任何意义的,return出去的变量值是不会发生改变的。
- finally当然也遵循着程序的顺序执行,若try中没有抛出异常,catch就不会被执行,那么就按照程序的先后顺序来进行顺序执行。先执行try语句块中的内容,再执行finally语句块中的内容,再执行finally语句块后面的内容。如果在finally里面return 了,则后面的语句都是没办法被执行到的了,因为程序到这里便结束了。
try{
}
catch(Exception ex){
//进行异常的代码,也可以在里面返回一个特殊的值,表示情况不对,有异常
}
finally{
}
try-catch-catch
写法一
注意:若采用写法一的写法,若第一个catch异常的实例对象已经包含了第二,第三个catch中的内容,当然就会报错。比如第一个catch的若是Exception,那后面的catch是不会被执行的,因为所有的异常都继承自Exception,第一个catch中捕获的异常已经包含了后面所有的异常,后面的catch已经没有了存在的意义。
所以,catch中的实例对象的关系只能是并行关系,即没有谁继承谁的说法,或者是从小到大的继承关系。
try {
throwMultiException(0);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
写法二
try {
throwMultiException(0);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
Try-with-resources
介绍
和资源有关的异常处理往往都比较繁复,尤其是有多个资源的时候,需要考虑如何在出现Exception的情况下把资源回收掉,关掉。这时候Try-with-resources就能很好的胜任这样的工作,在程序出现异常的时候能自动帮我们关闭资源。
语法格式
try(将需要关闭的资源放入括号里){
}catch(Exception ex){
}
程序示例
AutoCloseable
是一个资源回收的接口,实现这个接口就可以让try语句自动关闭资源。
public String read() 方法中使用了随机数,模拟读取文件的时候出现的错误,一旦出现错误,就会抛出异常。若文件在读取过程中出现了异常,我们要关闭掉该文件,不然可能造成之前读取的文件丢失。
import java.io.IOException;
public class MyAutoClosableResource implements AutoCloseable {
private String resName;
private int counter;
public MyAutoClosableResource(String resName) {
this.resName = resName;
}
public String read() throws IOException {
counter++;
if (Math.random() > 0.1) {
return "You got lucky to read from " + resName + " for " + counter + " times...";
} else {
throw new IOException("resource不存在哦");
}
}
@Override
public void close() throws Exception {
System.out.println("资源释放了:" + resName);
}
}
public class TryWithResource {
public static void main(String[] args) {
//TODO try括号里面的内容是创建的资源
//todo res1和res2都是两个文件资源,在while循环里不停地读资源,但是不知道什么时候就会出错,一旦出错了要把这两个文件正常的关闭掉
try (
MyAutoClosableResource res1 = new MyAutoClosableResource("res1");
MyAutoClosableResource res2 = new MyAutoClosableResource("res2")
) {
while (true) {
System.out.println(res1.read());
System.out.println(res2.read());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到,在Main方法里面我们没有调用close方法,但是在程序执行出现异常时中却执行了close方法释放资源/这就是try-with-resource
这个语法带来的功能。
Java中常见的异常
NullPointerException
:当尝试用为null的引用去调用方法的时候会出现这个问题。IndexOutOfBoundException
:数组索引出界ClassCastException
:当把一个类强行转化为另一个类的时候,如果两个类根本没有关系的时候就会出错ClassNotFoundException
:找不到类IOException
:和IO操作有关的Exception
前三个是Unchecked Exception
,后两个是Checked Exception
异常中的注意点
- 异常中最重要的信息是:
类型
,错误信息
和出错时的调用栈
- catch语句是根据异常类型匹配来捕捉相应类型的异常的,如果类型不匹配,catch语句是不会执行的,异常会继续抛出。
- catch(Throwable th)当然就可以捕获所有的异常,包括Error,但是建议最大只捕获Exception
- 如果catch一个其实并没有在try语句块中被抛出的checked exception,Java程序会报错,因为Java明确的知-道这个类型的异常不会发生。
- 如果catch一个unchecked exception,Java程序不会报错,即使try语句块不会抛出这样的异常
- 如果throws一个其实并没有被抛出的Checked exception或者Unchchecked exception,Java程序不会报错
关于Java中的异常处理机制
Java 的异常处理机制会带来两个相对昂贵的开销:
- try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
- Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。
Exception的扩展
Checked exceptions: Java’s biggest mistake
原文链接点此
译文链接点此
养成一个勤于记录的习惯,记录,让生活更美好。