一、异常类
首先,废话不多说,先上图,根据图,我们再来慢慢讲解
由上图我们可以知道,在java中,所有的异常类都继承自Throwable类。而Throwable类有两个实现类,分别是Error类和Exception类
1.Error
程序在执行过程中所遇到的硬件或操作系统的错误。错误对程序而言是致命的,将导致程序无法运行。是程序无法处理的错误,表示运行应用程序中较严重问题。大多数与程序员执行的操作无关,而表示代码运行时JVM出现的问题,这些异常发生时,JVM会选择线程终止。常见的错误有内存溢出(OOM)、虚拟机运行错误(Virtual MachineError)。
2.Exception
是程序正常运行中,可以预料的意外情况。是程序本身可以处理的异常。它又分为两个子类,分别是IOException,RuntimeException。
(1)IOException:
非运行时异常,javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。例如我们使用Thread.sleep方法,文件IO操作时,编译器会提醒我们加上try{}catch{}
(2)RuntimeException:
运行时异常,javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。但是在程序运行时会抛出异常,结束程序运行。比如我们在定义了数组之后,访问数组容量以外的元素会报错,产生ArrayIndexOutOfBoundsException;还有除数为0的操作等。
二、异常处理机制
在编写代码处理异常时,对于检查异常,一般有2种不同的处理方式:使用try…catch…finally语句块处理它。或者,使用throws 声明交给函数调用者caller去解决。
(1)使用throws例子:假如我们不同throws
(方法抛出异常用throws,try块抛出异常用throw)
public class Tts {
public int divide(int a,int b){
int res=a/b;
return res;
}
}
public class Tt {
public static void main(String[] args) throws InterruptedException{
Tts t = new Tts();
int res=0;
res = t.divide(5, 0);
System.out.println(res);
}
}
我们会发现程序并不会提醒我们需要用try catch处理异常,而当我们执行时,(除数为0)当然也就抛出了异常。
那当我们给divide方法加上throws Exception后,会发现编译器要求我们必须进行异常处理。,那么我们利用throws便可以提醒调用者此方法可能会有异常抛出,我们应该对异常进行强制相关处理。
public class Tt {
public static void main(String[] args) throws InterruptedException{
Tts t = new Tts();
int res=0;
try {
res = t.divide(5, 0);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("做处理。。。。。");
//e.printStackTrace();
}
System.out.println(res);
}
}
结果:
做处理。。。。。
0
(2)使用try{}catch{}(finally{})进行异常处理
通过上面的例子我们也就知道try{}catch{}是用来进行异常处理的,因为在程序运行时,我们总不能把异常显示给用户,体验不好,而且还有安全隐患。那么针对这种情况,我们一般都是对异常在前台进行隐藏并打印相关语句进行处理,例如上述的例子,异常发生时,我打印出“做处理。。。”而不是把异常结果直接抛给用户。
那么,关于try{}catch{}(finally{})的执行顺序又是如何的呢?
public class Tt {
public static void main(String[] args) throws InterruptedException{
char a = 'b';
int aa=a-97;
Tts t = new Tts();
int res=0;
try {
System.out.println("我会执行");
res = t.divide(5, 1);
System.out.println("我是否会执行?");
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("做处理。。。。。");
//e.printStackTrace();
}finally{
System.out.println("我一定要执行。。。。");
}
System.out.println(res);
}
}
还是刚刚的例子,当程序无异常时,情况如下:
我会执行
我是否会执行?
我一定要执行。。。。
5
public class Tt {
public static void main(String[] args) throws InterruptedException{
char a = 'b';
int aa=a-97;
Tts t = new Tts();
int res=0;
try {
System.out.println("我会执行");
res = t.divide(5, 0);
System.out.println("我是否会执行?");
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("做处理。。。。。");
//e.printStackTrace();
}finally{
System.out.println("我一定要执行。。。。");
}
System.out.println(res);
}
}
当程序发生异常时,情况如下:
我会执行
做处理。。。。。
我一定要执行。。。。
0
可以看到,不管是否发生异常,finally里面的代码一定会执行,而如果发生异常,在try中异常下面的代码不执行,如果不发生异常,catch中的代码不执行。
那如果搭配上return,程序的执行顺序又会如何呢?,我们对程序做如下修改:
public class Tts {
public int divide(int a,int b){
int res=0;
try {
System.out.println("我会执行");
res=a/b;
return res;
} catch (Exception e) {
// TODO Auto-generated catch block
return 55;
}finally{
System.out.println("我来啦");
return 11;
}
}
}
public class Tt {
public static void main(String[] args) throws InterruptedException{
char a = 'b';
int aa=a-97;
Tts t = new Tts();
int res=0;
res = t.divide(5, 1);
System.out.println(res);
}
}
结果:
我会执行
我来啦
11
public class Tt {
public static void main(String[] args) throws InterruptedException{
char a = 'b';
int aa=a-97;
Tts t = new Tts();
int res=0;
res = t.divide(5, 0);
System.out.println(res);
}
}
结果:
我会执行
我来啦
11
从上述结果来看,如果在finally中添加return,那么返回值会覆盖先前的return,不管是否出现异常。原因是jvm为方法创建了一个栈帧,而栈是先进后出的,那么后面执行的finally中的return就会先出栈返回,从而覆盖正确结果。
再将finally中的return注释掉
public class Tts {
public int divide(int a,int b){
int res=0;
try {
System.out.println("我会执行");
res=a/b;
return res;
} catch (Exception e) {
// TODO Auto-generated catch block
return 55;
}finally{
System.out.println("我来啦");
//return 11;
}
}
}
结果:
我会执行
我来啦
55
出现异常就会只return catch中的数。
三、自定义异常类
我们可以通过继承Exception类来生成一个自定义的异常,通常这个异常用来在捕获时产生一些我们想要的特定的影响(这里我觉得有点AOP的意思~~)这里就不再演示代码了,各位读者可以自己动手尝试一下。
四、Java中的异常处理关键字是什么?
throw: 有时我们明确要创建异常对象然后抛出它来停止程序的正常处理。throw关键字用于向运行时抛出异常来处理它。
throws: 当我们在方法中抛出任何已检查的异常而不处理它时,我们需要在方法签名中使用throws关键字让调用者程序知道该方法可能抛出的异常。调用方法可以处理这些异常或使用throws关键字将其传播给它的调用方法。我们可以在throws子句中提供多个异常,也可以与main()方法一起使用。
try-catch: 我们在代码中使用try-catch块进行异常处理。try是块的开始,catch是在try块的末尾处理异常。我们可以使用try有多个catch块,try-catch块也可以嵌套。catch块需要一个应该是Exception类型的参数。
finally: finally块是可选的,只能用于try-catch块。由于异常会暂停执行过程,因此我们可能会打开一些不会关闭的资源,因此我们可以使用finally块。finally块总是被执行,无论是否发生异常。
五、异常链
把捕获的异常包装成一个新的异常,在新的异常中添加对新的异常的引用,再把新异常抛出,就像是链式反应一样,这种就叫异常链。
public static void main(String[] args) {
A a = new A();// 创建chainTest实例
try {
a.test2();
} catch (Exception e) {
e.printStackTrace();
}
}
public void test1() throws DrunkException {
throw new DrunkException("喝车别开酒");
}
public void test2(){
try{
test1();
}catch (DrunkException e){
RuntimeException newExc=new RuntimeException("司机一滴酒亲人两行泪");//含参构造器
newExc.initCause(e);//调用newExc的init方法,把捕获的DrunkException传进去
throw newExc;//抛出新异常
}
}
结果
喝酒
java.lang.RuntimeException: 司机一滴酒亲人两行泪
at A.test2(A.java:37)
at A.main(A.java:22)
Caused by: DrunkException
at A.test1(A.java:30)
at A.test2(A.java:35)
... 1 more