异常处理是什么?
是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况。
Java会什么异常处理机制?
在这种情况下编程者需要另外进行错误检测(可能通过某些辅助全局变量如C的errno),或进行输入检验(如通过正则表达式),或者共同使用这两种方法。在现实代码中,异常处理是程序设计中一个很重要的方面,他可以大大简化程序员的工作量,好的异常处理机制的设计,可以更好对用户的输入进行提示,以防止程序崩溃。
二、Java异常的类图(来源网络,侵删)
三、Java异常的分类
由上面继承结构以清楚的看出,Java的异常类都继承自Throwable。Throwable有两个重要的子类,分别是:
Exception:指程序可以处理的异常,通俗来说,就是指的是,通过严谨的编码,可以很大程度避免这一异常发生。
Error:指的是程序无法处理的异常。这其中的大多数错误适合编码无关的,而是表示JVM的错误,比如内存溢出等。
在Exception中,又分为受检查(CheckedException)的异常和运行时(RuntimeException)的异常。CheckedException通俗的说表示直接继承Exception的那些子类。严谨的说,在继承关系中,没有RuntimeException的出现。那么另一种运行时异常就好理解了,就是在继承关系中,也就是有继承RuntimeException的类,就是运行时异常。
区别:
Checked:Java认为checked异常是可以被处理的,Java要求程序员必须显式处理checked异常,也就是通过throws或者try{}catch{}代码块来处理该异常。如果没有处理该异常,则程序在编译时期就会出错。(一般编译器都会提示的)
Runtime:顾名思义,运行时异常就是在编译时期不会出错,在程序运行时期,才会报错的异常。
为何Java不把所有异常都设置为checked异常呢?
个人认为是,比如除0,数组越界等异常,产生频繁,处理麻烦,如果大量的throws或者try...catch...会影响程序的执行效率和程序的可读性。
四、Java异常的一般处理方式
1>对于简单的checked异常来说:处理方式有两种,一种为throws,另一种为try {}catch{}
1.throws通俗用法和解析:
throws的用法简单,比如上面例子,当new FileInputStream()的时候,编译器会提示你,这里有一个checked异常,需要捕获或者抛出,这里则是选择throws,直接在方法名后throws <异常类名>就可以。但是注意,经过throws的异常在调用的时候,会在相应的调用方法的方法里面再次抛出,一层一层的向上抛。
这里出现三个问题。一,为什么在new FileInputStream()的时候编译器会提示要处理异常呢?二,为什么要抛出FileNotFoundException呢,抛出其他异常可不可以呢?三,最后一层一层的抛出异常,最后的异常会在哪里处理呢。别急,我们一步步解决:
为什么编译器会提示?既然是在new FileInputStream()的时候提示的,那么我们便去看看FileInputStream的源码:
这是FileInputStream的其中一个构造函数,显然,构造函数上也throws了该异常,这也符合了之前我们说的,经过throws的异常在调用的时候,会在相应的调用方法的方法里面再次抛出,一层一层的向上抛。
里面的代码很简单,就是调用了this(),也就是说调用了另一个构造函数。
一看这个构造函数,亮瞎眼的FileNotFoundException就出现在眼前,这里判断了该文件是否存在,或者说,该文件路径是否是一个有效的路径。如果无效,则throw一个FileNotFoundException。那么什么又是throw呢。其实,throw也是抛出异常,他和throws的区别在于,throw是语句抛出一个异常,后面跟的是一个异常对象。Throws是方法可能抛出异常的声明。为什么说可能?拿上文例子来说,new FileInputStream()并不一定会出现异常,如果传入的是有效的路径,FileInputStream可以通过该路径找到文件,那么就不会出现异常。这么一来,上文说的抛出异常就不对了。所以针对throws,应该叫做声明异常。而throw才是真正的抛出异常。
这么一来,就可以回答第一个问题了,正是这个构造方法里面会检查文件的有效性,那么如果无效,则会抛出异常,这个构造方法就有可能发生异常,那么就需要声明,即throws。
那么在test()和main()方法中,间接的调用了声明异常的方法,所以也要声明异常。
那第二个问题呢,为什么要抛出FileNotFoundException呢,抛出其他异常可不可以呢?
我们继续看源码:
通过注释可以知道,这是表示打开文件文件失败的异常。继承于IOException。其他什么也没实现。我们知道,Java是一门面向对象的语言,其中有一个特性叫做多态,那么我们在方法上声明IOException可不可以呢。
从代码可以看出,test方法完全没问题,可是main方法里面却报错了。这是因为在main方法的方法名后依然是FileNotFoundException。
这时就不会报错了,为什么会这样呢,这其实和父类与子类互相转换是一个道理。父类可以接受子类的对象,但是如果子类接受父类的对象不做其他处理,那么就会报错。
所以,抛出FileNotFoundException只是相当于标记一下而已,其他没什么作用。只要是声明的其父类,即抛出IOException/Exception等也是可以的。
那么第三个问题,一层一层的抛出异常,最后的异常会在哪里处理呢?这有两种情况,
一种是程序员自己捕获,比如:
具体之后解释,先看另一种:在main方法声明异常的情况。
运行程序,会直接报出一下异常:
反编译class文件。
可以看见,这个类只执行了两行代码,第一行,调用main类的构造方法,第二行,执行main方法,然后便终止该程序。也就是说,当我们一层一层向上抛的时候,如果程序员不曾处理,那么便是交给JVM来进行处理的。
2>try{}catch{}
代码会执行try{}代码里面的代码,当发生异常后,便会执行catch代码块里面的代码。而程序员解决异常,多采用捕获这一种方法。这是因为捕获能够有更好的操作性。比如,在catch代码块里面,可以不用抛给JVM,有自己处理。可以不用终止程序。
try{}catch{}一般还与finally搭配使用,此处不与谈论。
关于Error的处理:
Error各种各样,没有具体的想exception一样系统的处理方法,但是Error是可以catch的,而且也可以向常规Exception一样被处理,而且就算不捕捉的话也只是导致当前线程挂掉,其他线程还是可以正常运行,如果有需要的话捕捉Error之后也可以做些其他处理。但是Error是一种系统内部的错误,这种错误不像Exception一样是可能是程序和业务上的错误是可以恢复的。
五、Java异常的深入分析
Java所有的异常,其实只是起了一个标记而已,所有的类中都没有具体的实现,全部实现,全部是在Throwable里面。那么Throwable又做了什么呢?
Throwable实现了serializable,即表示可序列化。
Throwable里面实现了四个内部类:
SentineHolder是一个用于序列化的内部类,里面维护了两个StackTranceElement数组,一个数组用来储存未来可能发生变化的堆栈轨迹值,一个用于指示不可变的堆栈轨迹的值。
后面仨和输入输出有关,而打印异常时便是pritstreamorwirter类,里面有抽象方法lock和println。其他俩个为pritstreamorwirter的子类,其中lock返回printstream和printwirter。println使用了代理模式。
内部有一个cause对象,指向了this,即初始化异常的原因为本身。
方法中第一次判断代表了如果设置过,就抛异常,即cause只能设置一次。
第二次判断表示参数为本身对象,也会抛出异常。
Native方法,用于获取执行的栈轨迹。
打印方法:
首先输出自身,然后打印每一个栈轨迹。然后打印每个造成异常的原因的对象。
六、Java异常的总结
Java异常时Java中很重要的一部分。总结一下很有必要。