目录
学习笔记
Java语言提供最为强大的支持就在异常的处理与操作上。
认识异常
异常是指导致程序中断运行的指令流。
范例:观察没有异常产生
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
System.out.println("【2】********数学计算:"+ (10/2)) ;
System.out.println("【3】********程序执行完毕*********") ;
}
}
在程序执行正常的过程可以发现,所有程序会按照既定的结构从头到尾执行。
范例:产生异常
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
System.out.println("【2】********数学计算:"+ (10/0)) ;
System.out.println("【3】********程序执行完毕*********") ;
}
}
出现错误之后整个程序将不会按照既定的方式继续执行,而是中断执行。为了保证程序出现非致命错误之后,程序依然可以正常完成,所以需要完善的异常处理机制,以保证程序的顺利执行。
处理异常
在Java中进行异常的处理,使用:try、catch、finally,这几个关键字来完成,其基本处理结构如下:
try{
// 可能出现异常的语句
}[catch(异常类型 异常对象){
// 异常处理
}catch(异常类型 异常对象){
// 异常处理
}catch(异常类型 异常对象){
// 异常处理
}....][finally{
// 不管异常是否处理都要执行
}]
此格式中可以执行的组合:try...catch、try...catch....finally、try....finally。
范例:处理异常
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try{
System.out.println("【2】********数学计算:"+ (10/0)) ;
}catch(ArithmeticException e){
System.out.println("【c】 处理异常" + e) ; // 处理异常
}
System.out.println("【3】********程序执行完毕*********") ;
}
}
此时可以发现,即便发现了异常程序依然可以执行完毕,所以此时的设计属于合理设计。但有一个问题出现了,此时在异常处理时输出的是一个异常处理类的对象,那么对于此对象如果直接打印(t调用oString())所得到的异常信息并不完整,如果要想获得非常完整的异常信息,则可以使用异常类中提供的printStackTrace()完成。
范例:获取完整异常信息
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try{
System.out.println("【2】********数学计算:"+ (10/0)) ;
}catch(ArithmeticException e){
e.printStackTrace() ; // 处理异常
}
System.out.println("【3】********程序执行完毕*********") ;
}
}
对于异常的处理格式可以在最后加上finally的处理块,表示异常处理的出口,不管是否出现异常都执行。
范例:使用finally语句
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try {
System.out.println("【2】********数学计算:"+ (10/0)) ;
} catch(ArithmeticException e){
e.printStackTrace() ; // 处理异常
} finally{
System.out.println("【F】不管是否出现异常我都执行") ;
}
System.out.println("【3】********程序执行完毕*********") ;
}
}
此时程序中有异常执行finally,没有异常也执行finally。
处理多个异常
很多时候,在程序执行过程中可能出现多个异常,可以使用多个catch进行异常的捕获。现在假设通过初始化的参数来进行数学计算的设置,如下:
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try {
int x = Integer.parseInt(args[0]) ;
int y = Integer.parseInt(args[1]) ;
System.out.println("【2】********数学计算:"+ (x/y)) ;
} catch(ArithmeticException e){
e.printStackTrace() ; // 处理异常
} finally{
System.out.println("【F】不管是否出现异常我都执行") ;
}
System.out.println("【3】********程序执行完毕*********") ;
}
那么对于此时的程序就有可能产生三类异常:
-
【未处理】程序执行的时候没有输入初始化参数(JavaDemo):ArrayIndexOutOfBoundsException
-
【未处理】输入时的数据不是数字(JavaDemo A B):NumberFormatException
-
【已处理】输入的被除数为0(JavaDemo 10 0): ArithmeticException
现在即便有了异常处理语句,但是如果没有进行正确的异常捕获,那么程序也会导致中断,但是finally的代码依然执行,所以在这样的情况下,需要捕获多个异常。如下:
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try {
int x = Integer.parseInt(args[0]) ;
int y = Integer.parseInt(args[1]) ;
System.out.println("【2】********数学计算:"+ (x/y)) ;
} catch(ArithmeticException e){
e.printStackTrace() ; // 处理异常
} catch(NumberFormatException e){
e.printStackTrace() ; // 处理异常
} catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace() ; // 处理异常
} finally{
System.out.println("【F】不管是否出现异常我都执行") ;
}
System.out.println("【3】********程序执行完毕*********") ;
}
}
此时开发者已经明确知道有哪些异常,为什么要使用异常处理?直接多写点判断就可以?
异常处理流程
在进行异常处理的时候,对所有可能产生的异常都进行捕获,虽然你可以得到非常良好的代码结构,这种代码编写是非常麻烦的,所以现在进行合理异常就必须清楚异常产生之后程序做了那些处理。
1、在程序运行的过程中才会产生异常,一旦程序运行过程中产生异常将自动进行指定类型的异常类对象实例化处理。
2、如果此时程序之中没有提供异常处理的支持,则会采用JVM的默认异常处理方式,首先打印异常信息,然后中断执行。
3、此时程序中如果存在异常处理,那么这个产生的异常类的实例化对象,将会被try语句进行捕获;
4、try捕获到异常之后,将会与其匹配的catch中的异常进行依次比对;如果此时与catch中的异常类型相同,应该用此catch进行异常处理,如果没有任何的catch匹配成功,那么就表示此异常无法进行处理;
5、不管异常时候处理,最终都要执行finally语句。但是在执行完成finally的程序之后会进一步判断当前的异常是否处理过了,如果处理过了则向后执行其代码,如果没有处理,则交由JVM进行默认的处理。
通过分析可以发现,在整个异常处理的流程之中,实际上操作的还是一个异常类的实例化对象,那么这个这个异常类的实例化对像的类型就成为理解异常处理的核心关键所在,在之前接触的两种异常:
ArithmeticException:
- java.lang.Object
-
- java.lang.Throwable
-
- java.lang.Exception
-
- java.lang.RuntimeException
-
- java.lang.ArithmeticException
ArrayIndexOutOfBoundsException:
- java.lang.Object
-
- java.lang.Throwable
-
- java.lang.Exception
-
- java.lang.RuntimeException
-
- java.lang.IndexOutOfBoundsException
-
- java.lang.ArrayIndexOutOfBoundsException
可以发现在程序之中,可以处理的最大异常类型是Throwable,可以观察在Throwable中提供两个子类:
- Error:此时程序还未执行出现的错误,开发者没法处理。
- Exception:程序中出现的异常,开发者可以处理,真正在开发之中需要关注的是Exception
通过分析可以发现,异常产生的时候会产生异常的实例化对象,那么按照对象的引用原则会自动的向父类转型,那么按照这样的逻辑,所以实际上所有的异常都可以使用Exception处理。
范例:简化异常处理
public class JavaDemo{
public static void main(String[] args){
System.out.println("【1】********程序开始执行*********") ;
try {
int x = Integer.parseInt(args[0]) ;
int y = Integer.parseInt(args[1]) ;
System.out.println("【2】********数学计算:"+ (x/y)) ;
} catch(Exception e){
e.printStackTrace() ; // 处理异常
} finally{
System.out.println("【F】不管是否出现异常我都执行") ;
}
System.out.println("【3】********程序执行完毕*********") ;
}
}
当你不确定产生那种异常的时候,这种处理方法是最方便的。但是如果这样处理也会产生一个问题,这种异常的处理方式虽然方便但是描述的错误信息不明确,分开处理异常时更加明确的处理方式。
在以后进行多个异常捕获处理的时候,要把捕获范围大的异常放在捕获范围小的之后。
throws关键字
通过之前的程序可以发现,在程序的执行过程之中,有可能会产生异常,但是假设你定义了一个方法,就需要明确的告诉使用者这个方法可能会产生何种异常,那么此时就可以在方法的申明上使用throws关键字来进行异常类型的标注。
范例:观察throws的使用
class MyMath{
// 这个代码执行的时候可能会产生异常, 如果产生异常了调用处处理
public static int div(int x, int y) throws Exception {
return x / y ;
}
}
public class JavaDemo{
public static void main(String[] args){
try {
System.out.println(MyMath.div(10, 0) ) ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
主方法本身也可以继续向上抛出,
范例:在主方法上继续抛出异常
class MyMath{
// 这个代码执行的时候可能会产生异常, 如果产生异常了调用处处理
public static int div(int x, int y) throws Exception {
return x / y ;
}
}
public class JavaDemo{
public static void main(String[] args) throws Exception {
System.out.println(MyMath.div(10, 0) ) ;
}
}
如果主方法继续向上抛出异常,就是表示此异常继续交由JVM负责处理。
throw关键字
与throws对应的还有throws关键字,此关键字的主要作用是表示手工进行异常抛出,即:此时将手工产生一个异常类的实例化对象,并且进行异常的抛出处理。
范例:观察throw的使用
public class JavaDemo{
public static void main(String[] args){
try { // 异常对象不是再由系统生成的,而是由手工定义的
throw new Exception("自己抛着玩的对象") ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
}
面试题:请解释trow与throws的区别?
-
throw:是在代码块中使用的,主要的手工进行异常对象的抛出
-
throws:是在方法定义上使用的,表示将此方法中产生的异常明确告诉调用处,由调用处进行处理。
异常处理标准格式
现在已经完成了大部分的处理格式:try、catch、finally、throw、throws,这些关键字在实际开发中往往会一起进行使用,下面通过具体的程序进行分析。
现在要求定义一个可以实现除法要求的方法,在这个方法之开发要求如下:
-
在进行数学计算开始与结束的时候进行信息提示;
-
如果在计算之中出现异常则交由调用处处理。
class MyMath{
// 异常要交给被调用处处理,则一定要在方法上使用throws.
public static double div(int x, int y) throws Exception{
double temp = 0 ;
System.out.println("**【START】除法计算开始**") ;
try {
temp = x / y ;
} catch (Exception e){
throw e ; // 向上抛异常对象
} finally {
System.out.println("**【END】除法计算结束**") ;
}
return temp ;
}
}
public class JavaDemo{
public static void main(String[] args){
try {
System.out.println(MyMath.div(10,0)) ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
}
用于网络连接:将计算开始结束,改为”网络连接、网络连接关闭“;
对于此类操作可以简化省略掉catch与throw的操作。
class MyMath{
// 异常要交给被调用处处理,则一定要在方法上使用throws.
public static double div(int x, int y) throws Exception{
double temp = 0 ;
System.out.println("**【START】除法计算开始**") ;
try {
temp = x / y ;
} finally {
System.out.println("**【END】除法计算结束**") ;
}
return temp ;
}
}
public class JavaDemo{
public static void main(String[] args){
try {
System.out.println(MyMath.div(10,0)) ;
} catch (Exception e) {
e.printStackTrace() ;
}
}
}
在以后的实际开发之中,这种处理格式是非常重要的,特别与一些资源进行访问操作的时候尤其重要。
RuntimeException
通过之前的分析,只要方法后面带有throws的,往往都是告诉用户本方法产生的异常是什么。下面观察一段代码:
public class JavaDemo{
public static void main(String[] args){
int num = Integer.parseInt("123") ;
System.out.println(num) ;
}
}
打开Integer()类中,parseInt()方法的定义来观察:public static int parseInt(String s) throws NumberFormatException。这个方法上明确的抛出了异常,但是在处理的时候并没有强制要求处理,观察NumberFormatException的继承结构,观察数学异常的继承结构。
ArithmeticException:
- java.lang.Object
-
- java.lang.Throwable
-
- java.lang.Exception
-
- java.lang.RuntimeException
-
- java.lang.ArithmeticException
NumberFormatException:
- java.lang.Object
-
- java.lang.Throwable
-
- java.lang.Exception
-
- java.lang.RuntimeException
-
- java.lang.IllegalArgumentException
-
- java.lang.NumberFormatException
如果现在所有的程序执行上只要使用了throws定义的方法,都必须要开发者进行手工处理,那么这个代码的编写就太麻烦了。所以在设计的过程之中,考虑到代码的编写方便,所以提供由一个灵活的可选的异常处理父类”RuntimeException“,这个类的子类不需要强制性处理。
面试题:请解释RuntineException与Exception的区别?请例举出几个你常见的RuntimeException?
-
RuntimeException是Exception的子类,
-
RuntimeException标注的异常可以不需要进行强制性处理,而Exception异常必须强制性处理;
-
常见的RuntimeException异常:NumberFormatException、
ClassCastException、
NullPointerException等。
自定义异常类
在JDK之中提供大量的异常类型,但是在实际开发之中可能这些异常类型未必够你所使用,你不可能所有的设计里面抛出Exception,所以这个时候就需要考虑自定义异常类。但是自定义异常有两种操作方式,继承Exception 或 继承RuntimeException。
范例:实现自定义异常
class BombException extends Exception {
public BombException(String msg){
super(msg) ;
}
}
class Food{
public static void eat(int num) throws BombException {
if(num > 10){
throw new BombException("吃太多了,肚子爆了") ;
} else {
System.out.println("正常吃") ;
}
}
}
public class JavaDemo{
public static void main(String[] args) throws BombException{
Food.eat(11) ;
}
}
在以后的项目开发过程中,会接触大量的自定义异常处理,如果出现了你不清楚的异常,最简单方式通过搜索引擎查询一下异常产生的原因。
assert关键字
从JDK1.4开始追加了断言的功能,确定代码执行到某行处一定是所期待的结果。在实际的开发之中,对于断言而言并不一定是准确的有可能出现偏差,这种偏差不应该影响程序的正常执行。
范例:断言的使用
public class JavaDemo{
public static void main(String[] args){
int x = 10 ;
// 中间经过许多x变量的操作
assert x == 100 : "x的变量不是100" ;
System.out.println(x) ;
}
}
如果现在要想执行断言,则必须在程序执行的时候加入参数: -ea
所以在Java中并没有将程序断言设置为必须执行的步骤,需要特定环境下才能开启。