目录
异常产生原因
异常体系
异常发生的过程
异常的分类
异常处理机制
throw与throws关键字的讲解
throw关键字
throws关键字
编译异常的两种处理方式
Throwable类中三个处理异常的方法
finally代码块
捕获异常的例子
异常处理原则
try catch finnaly的几种组合方式
异常的注意事项
多异常的捕获处理
多个异常分别处理
多个异常一次捕获,多次处理
多个异常一次捕获一次处理
finally中有return语句
子父类抛出异常规则
自定义异常类
Throwable类中定义了3个异常处理的方法
String getMessage() 返回此throwable的简短描述。
String toString() 返回此throwable的详细消息字符串。
void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的
对比三个方法分别放在catch里控制台打印的异常信息:
e.getMessage:文件的后缀名不对
e.toString:java.io.IOException: 文件的后缀名不对
e. printStackTrace:
java.io.IOException: 文件的后缀名不对
at com.itheima.demo02.Exception.Demo01TryCat ch.readFile(Demo01TryCatch.java:55)
at com.itheima.demo02.Exception.Demo01TryCatch.main(Demo01TryCatch.java:27)
所以说最后一个方法打印的信息是最全面的,包括异常名称,位置,内容等信息,同时它也是Jvm默认的异常处理机制
平常我们通过if判断条件符合的话进入问题处理代码中,这样在if条件外部的正常流程代码和问题处理代码耦合性强,不利于阅读,所以我们可以将问题处理代码封装到一个对象中,将问题处理代码和正常流程代码相分离,而异常就是起到这样一个封装问题代码的作用,异常对象通常都封装了问题的名称,信息,位置等,在java中用类的形式对不正常情况进行了描述和封装对象,这种描述不正常的类,就称为异常类,不同的问题用不同的类进行具体的描述,比如说角标越界,空指针异常等,体现了面向对象的思想
异常的概念:运行时期发生的不正常现象
不正常情况体系:(不正常情况分为两大类)
顶层父类:Throwable
Throwable:无论是错误还是异常,问题发生了就应该可以抛出,让调用者知道并处理,该体系的特点就在于Throwable及其所有的子类都具有可抛性,可抛性通过两个关键字来体现:1.throw 2.throws
1.error:一般不可处理的(癌症):特点:是由系统反馈给jvm,jvm抛出的严重性系统级问题,这种问题发生一般不针对性处理,直接修改源程序
2.exception :可以处理的(感冒,发烧),把有可能发生异常的程序处理一下,程序可以继续执行
当发生异常时,函数的源头会生成异常对象(这个异常问题是在java中定义好的,如果没有定义,需要我们自定义异常类)抛给调用者main(主函数),程序不再往下执行,主函数收到问题,看主函数有没有处理,若没有,则主函数抛给主函数的调用者jvm,主函数也不继续向下执行,jvm收到问题后,做了最终处理,将问题对象(异常)反馈给我们,将异常的位置,信息,名称等显示在屏幕上,让我们知道。当然我们也可以手动抛出,这时候用throw关键字,利用异常的构造函数生成异常对象,例如 throw new XxxException();至于构造函数的参数就看这个异常类有什么构造方法了,一般可以为空,index整型常量,String等,这个构造函数里面的参数就是控制我们所能看到的异常信息
代码分析示例截图:
运行时异常(RuntimeException)和非运行时(编译)异常,这两种异常有很大的区别,也称之为编译时不检查异常(Unchecked Exception)和编译时检查异常(Checked Exception)
从类的继承结构上来说
编译时检查异常:只要是Exception和其他子类都是这类异常,除了特殊子类RuntimeException,没有继承RuntimeException的异常,编译异常必须进行预处理,否则语法错误,预处理是指开发人员对将来可能产生的异常提出一个处理方案
运行异常:是继承了RuntimeException的异常,运行时异常不需要预处理
编译时不检查异常:其中Exception有一个特殊的子类RuntimeException,以及RuntimeException的子类是运行异常,也就说这个异常是编译时不被检查的异常,也叫运行异常(解释:如果自定义异常继承的是Exception,编译时会检查这个异常是否声明捕获或者抛出,如果不进行这两个操作,则编译(javac)不通过,而如果自定义异常继承的是RuntimeException,编译时则不检查这个异常,直接通过,无需声明或者抛出)
异常有两种:
1.编译时异常
该异常在编译时,如果没有处理(没有抛也没有try),编译失败。
2.运行时异常(编译时不检测)
在编译时不需要处理,编译器不检查。该异常的发生,建议不处理,让程序停止。需要对代码进行修正。运行时异常可以通过我们规范的代码去避免产生,例如空指针异常,我们可以进行空判断
当程序中出现编译时异常,要么用try-catch语句捕获它,要么用throws语句声明抛出它,否则编译不会通过,必须做捕获或者声明。指一类可以预知的,当发生异常后知道如何处理的异常,所以可以捕获处理,这样的问题都可以有针对性的处理,编译时异常,当函数体内throw了一个非RuntimeException,而函数名上并没有声明该异常告诉上层调用者,那么javac 编译器就认为这个代码是有安全隐患的,不允许编译通过(就好比你帮朋友买了一个面包(编译时异常),而这个面包已经放了两天了,可能长毛了,那你拿给你朋友(调用者)的时候,如果不跟他说,这个面包可能过期吃了会肚子疼,这样就会有安全隐患,所以你需要写个小纸条:面包可能过期长毛,去告诉你朋友这个情况(而你告诉他的这个举动就相当于声明异常,也就是在函数上throws new XxxException )),这样你朋友就可以针对这个情况做出预先的处理方式,这样你朋友可以通过微波炉加热或者其他处理方式处理这个面包,自己吃下去也就不会出现问题了。而这个朋友进行的预处理方式其实就是我们所说的捕捉异常
而运行时异常,表示一类 未知的不确定的只有在运行时才会出现的异常。这种问题的发生,无法让功能继续,运算无法运行。更多的是因为调用者的原因引起的,例如:瞎传参数,这种问题一般不处理,直接编译通过,在运行时,让调用者调用时的程序强制停止,让调用者对源代码进行修正。
作用:
可以使用throw关键字在指定的方法中抛出指定的异常
使用格式:
throw new xxxException("异常产生的原因");
注意:
1.throw关键字必须写在方法的内部
2.throw关键字后边new的对象必须是Exception或者Exception的子类对象
3.throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
throw关键字后边创建的是RuntimeException或者是 RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
代码示例:
public static void main(String[] args) { //int[] arr = null; int[] arr = new int[3]; int e = getElement(arr,3); System.out.println(e); } /* 定义一个方法,获取数组指定索引处的元素 参数: int[] arr int index 以后(工作中)我们首先必须对方法传递过来的参数进行合法性校验 如果参数不合法,那么我们就必须使用抛出异常的方式,告知方法的调用者,传递的参数有问题 注意: NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理 ArrayIndexOutOfBoundsException是一个运行期异常,我们不用处理,默认交给JVM处理 */ public static int getElement(int[] arr,int index){ /* 我们可以对传递过来的参数数组,进行合法性校验 如果数组arr的值是null 那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是null" */ if(arr == null){ throw new NullPointerException("传递的数组的值是null"); } /* 我们可以对传递过来的参数index进行合法性校验 如果index的范围不在数组的索引范围内 那么我们就抛出数组索引越界异常,告知方法的调用者"传递的索引超出了数组的使用范围" */ if(index<0 || index>arr.length-1){ throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围"); } int ele = arr[index]; return ele; }
throws关键字:异常处理的第一种方式,交给别人处理
作用:
当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理-->中断处理(程序不会再向下执行)
使用格式:在方法声明时使用
修饰符 返回值类型 方法名(参数列表) throws AAAExcepiton,BBBExcepiton...{
throw new AAAExcepiton("产生原因");
throw new BBBExcepiton("产生原因");
...
}
注意:
1.throws关键字必须写在方法声明处
2.throws关键字后边声明的异常必须是Exception或者是Exception的子类
3.方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常
如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
4.调用了一个声明抛出异常的方法,我们就必须得处理声明的异常
要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM
要么try...catch自己处理异常
编译时异常的两种处理方式:声明或者捕捉
1.声明就不多说了,直接在函数上throws new XxxException告知调用者,调用者获取到的异常就是在函数名上由throws抛出才得到的,如果函数名上没抛出异常,则调用者处压根没异常去声明或者捕捉,这种方式的缺点在于假如说抛出了异常,那么程序就会被终止,异常后面的代码是不会继续执行的
2.捕捉异常:这是对异常进行针对性处理的方式,如下图
try...catch:异常处理的第二种方式,捕捉异常
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}catch(异常类名 变量名){
}
注意:
1.try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
2.如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try...catch之后的代码
如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try...catch之后的代码
(注意:在try里如果有两行代码,第一行就产生了异常,进入了catch,那么在try中第二行的代码是不会被执行到的)
Throwable类中定义了3个异常处理的方法
String getMessage() 返回此throwable的简短描述。
String toString() 返回此throwable的详细消息字符串。
void printStackTrace() JVM打印异常对象,默认此方法,打印的异常信息是最全面的
对比三个方法分别放在catch里控制台打印的异常信息:
e.getMessage:文件的后缀名不对
e.toString:java.io.IOException: 文件的后缀名不对
e. printStackTrace:
java.io.IOException: 文件的后缀名不对
at com.itheima.demo02.Exception.Demo01TryCat ch.readFile(Demo01TryCatch.java:55)
at com.itheima.demo02.Exception.Demo01TryCatch.main(Demo01TryCatch.java:27)
所以说最后一个方法打印的信息是最全面的,包括异常名称,位置,内容等信息,同时它也是Jvm默认的异常处理机制
格式:
try{
可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出的异常对象){
异常的处理逻辑,异常异常对象之后,怎么处理异常对象
一般在工作中,会把异常的信息记录到一个日志中
}
...(多个catch)
catch(异常类名 变量名){
}finally{
无论是否出现异常都会执行
}
注意:
1.finally不能单独使用,必须和try一起使用
2.finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
首先主函数调用了show方法,show方法手动将一个异常对象(new exception)抛给了主函数,然后主函数尝试去解决异常 ,try检测到了异常,然后将这个对象直接给了ex参数,catch接收了进来,然后就处理异常,再继续执行下去。
一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会,同时尽量将捕获相对高层(范围较大)的异常类的catch子句放在后面。否则,捕获底层异常类(范围较小,比较有针对性)的catch子句将可能会被屏蔽
(这里的异常都是指编译时异常,也就是受检查异常,运行时异常不用遵循下面的原则)
try catch:对代码进行异常检测,并将检测到的异常对象扔给catch进行处理,简称异常捕获处理,当没有必要进行资源释放时,可以不用定义finally
try finnaly:对代码进行异常检测,因为没有捕获异常,所以需要在函数上声明异常,但是功能所开启的资源需要关闭,目的就是为了释放资源
Try catch finnaly:正常流程:检测,捕捉,释放
多个异常分别处理的意思是将多个都可能发生异常的代码放在多个try进行包裹,再用catch进行异常接收,示例代码如下:
try { int[] arr = {1,2,3}; System.out.println(arr[3]);//ArrayIndexOutOfBoundsException: 3 }catch (ArrayIndexOutOfBoundsException e){ System.out.println(e); } try{ List
list = List.of(1, 2, 3); System.out.println(list.get(3));//IndexOutOfBoundsException: Index 3 out-of-bounds for length 3 }catch (IndexOutOfBoundsException e){ System.out.println(e); }
一次捕获多次处理的意思是,只用一个try包裹有可能发生问题的代码块,但是用多个catch进行异常接收,这种方式的注意点是catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在上边,否则就会报错(因为如果范围大的父类写在上面,那么子类永远也不可能接收到这个异常,因为就算发生了本身子类的异常,那么父类也可以多态的方式进行接收),另外我还想强调一旦第一个异常发生,被下列的catch中的异常类接收的时候,那么try中的代码会终止,不会再向下执行,等catch中的异常代码处理结束,再执行try catch代码块以外的后续代码,示例代码如下:
try { int[] arr = {1,2,3}; System.out.println(arr[3]);//ArrayIndexOutOfBoundsException: 3 List
list = List.of(1, 2, 3); System.out.println(list.get(3));//IndexOutOfBoundsException: Index 3 out-of-bounds for length 3 }catch (ArrayIndexOutOfBoundsException e){ System.out.println(e); }catch (IndexOutOfBoundsException e){ System.out.println(e); } 上面的ArrayIndexOutOfBoundsException是IndexOutOfBoundsException的子类
一次捕获一次处理的意思是在catch的时候可以用try代码块中可能发生的异常的统一父类来接收,比如Exception,示例代码如下:
try {
int[] arr = {1,2,3};
//System.out.println(arr[3]);//ArrayIndexOutOfBoundsException: 3
List list = List.of(1, 2, 3);
System.out.println(list.get(3));//IndexOutOfBoundsException: Index 3 out-of-bounds for length 3
}catch (Exception e){
System.out.println(e);
}
如果finally有return语句,永远返回finally中的结果,要避免该情况
示例代码如下:
//定义一个方法,返回变量a的值,此时返回a的结果是100,而不是10
public static int getA(){ int a = 10; try{ return a; }catch (Exception e){ System.out.println(e); }finally { //一定会执行的代码 a = 100; return a; } }
1、子类在覆盖父类方法时,父类的方法如果抛出了异常,那么子类的方法只能抛出父类的异常或者该异常的子类或者不抛出异常。(比如说父类的方法抛出了异常A,那么子类重写父类的方法也只能抛出异常A或者A的子类,或者不抛出异常)
2、如果父类抛出多个异常,那么子类只能抛出父类异常的子集或者不抛出异常。(比如说父类抛出了A,B,C,D,那么子类中重写的方法只能抛出这四个类本身或者这四个类的子集或者不抛)
简单说:子类覆盖父类只能抛出父类的异常或者子类或者子集或者不抛出异常
注意:如果父类的方法没有抛出异常,那么子类覆盖时绝对不能抛。只能try
自定义异常类:
java提供的异常类,不够我们使用,需要自己定义一些异常类
格式:
public class XXXExcepiton extends Exception | RuntimeException{
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1.自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2.自定义异常类,必须的继承Exception或者RuntimeException
继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期
异常,就必须处理这个异常,要么throws,要么try...catch
继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)
代码示例:
public class RegisterException extends /*Exception*/ RuntimeException{ //添加一个空参数的构造方法 public RegisterException(){ super(); } /* 添加一个带异常信息的构造方法 查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常信息 */ public RegisterException(String message){ super(message); } }