异常
异常是指在程序的运行过程中所发生的的不正常事件,它会中断正在运行的程序.
程序运行 ---->异常 ---->程序中断运行
在写程序代码的时候,总会难免出现一些异常(Bug).如果每发现一个异常就去加条件限制来修复异常,代码就会显得臃肿,并且需要耗费大量精力去堵漏洞.这时候就需要在程序中加入异常处理机制.
异常处理机制:
程序中预先设置好对付异常的处理方法 ---->程序运行 —>异常 —>对异常进行处理 —>处理完毕,程序继续运行(不会中断程序运行)
Java中的异常处理是通过5个关键字来实现的,分别是:try、catch、finally、throw、throws
try-catch结构
使用try-catch块捕获异常,会有三种情况出现:
1、程序正常运行
public void method(){
try{
//代码段(此处不会产生异常)
}catch(异常类型 ex){
//对异常进行处理的代码段
}
//代码段
}
在这种情况下,程序执行完try块中的代码段后,将不会进入catch里的代码,而直接执行catch代码块后面的代码段.
2、出现异常,并且异常类型匹配
public void method(){
try{
//代码段1
//产生异常的代码段2
//代码段3
}catch(异常类型 ex){
//对异常进行处理的代码段4
}
//代码段5
}
在这种情况下,代码在try块进行到异常的代码段2后,就不再执行代码段3,而是跳到catch中去与捕捉异常类型进行匹配,匹配成功后,就会执行catch中的代码段4,然后再进行catch后的代码段5.
3、出现异常,但异常类型不匹配
public void method(){
try{
//代码段1
//产生异常的代码段2
//代码段3
}catch(异常类型 ex){
//对异常进行处理的代码段4
}
//代码段5
}
这种情况下,程序在运行到try块中的代码段2后,不再运行代码段3,而是跳到catch中去进行异常类型匹配判断,发现类型匹配不上,程序直接终止,不再执行后面的代码.
在catch块中处理异常,有以下几个方法可以用到:
System.err.println(String msg);
加入用户自定义处理信息,并在控制台以红色字体打印输出信息
(void) printStackTrace();
调用方法输出异常信息,输出异常的堆栈信息.
(String) getMessage();
返回异常信息描述字符串,是printStackTrace()输出信息的一部分内容
常见的异常类型
异常类型 | 说明 |
---|---|
Exception | 异常层次结构的父类 |
ArithmeticException | 算术错误情形,如以零作为除数 |
ArrayIndexOutOfBountsException | 数组下标越界 |
NullPointerException | 尝试访问null对象成员 |
ClassNotFoundException | 不能加载所需的类 |
IllegalArgumentException | 方法接受到非法参数 |
ClassCastException | 对象强制类型转换出错 |
NumberFormatException | 数字格式转换异常,如把"abc"转换成数字 |
try-catch-finally结构
try{
//代码块1
}catch(异常类型 ex){
//处理异常的代码块2
}finally{
//代码块3
}
在try-catch块后面加入finally块,无论程序运行是否有异常,finally块里的代码段都会执行
无异常时,try —>finally
有异常时,try —> catch —> finally
finally代码块唯一不执行的一种情况是,在try或catch中出现System.exit(i);语句。
当运行到System.exit(1)语句时,程序中断,退出Java虚拟机,finally中的代码将不执行。
需要注意的是,try-catch-finally结构中,try语句块是必须的,但catch和finally语句块可选择写或不写,但二者必须至少出现一个,即try-catch、try-finally、try-catch-finally三种。
存在 return的try-catch-finally结构
public void method(){
try{
//代码段1
return;
}catch(异常类型 ex){
//对异常进行处理的代码段3
}finally{
//代码段4
}
}
public void method(){
try{
//代码段1
//产生异常的代码段2
}catch(异常类型 ex){
//对异常进行处理的代码段3
return;
}finally{
//代码段4
}
}
在以上两段代码中,分别为在try块中加入了return和在catch块中加入了return,无论是哪种情况,程序运行都将先运行finally块中的代码,最后才执行return跳出方法。
多重catch块
在一段代码中,运行时可能会引发不止一种异常,这时候异常处理的catch捕获可以使用多重catch来捕获多种类型的异常,但有几点需要注意:
1、排列catch语句的顺序:先子类后父类
2、发生异常时按顺序逐个匹配
3、只执行第一个与异常类型匹配的catch语句
try{
//异常代码段1
}catch(异常类型1 a){
//处理异常1的代码段
}catch(异常类型2 b){
//处理异常2的代码段
}catch(异常类型3 c){
//处理异常3的代码段
}
...
}finally{
//代码段4
}
声明异常
如果在一个方法体中抛出了异常,为了通知调用者,需要用throws来声明某个方法可能抛出的各种异常,多个异常用逗号隔开。
public void divide() throws ArithmeticException,InputmismatchException{
.....
}
调用者在调用存在异常的方法时,需要自己处理方法中的异常,或者继续声明异常,让更外一层的方法来处理异常。如果在main()方法中声明的异常,就由Java虚拟机进行处理。
(1)继续声明异常
public static void main(String[]args) throws ArithmeticException,InputmismatchException{
Test t =new Test();
t.divide();
}
(2)调用者处理异常
public static void main(String[]args){
Test t =new Test();
try{
t.divide();
}catch(ArithmeticException a){
//代码块1
}catch(InputmismatchException i){
//代码块2
}finally{
//代码块3
}
}
抛出异常
除了系统自动抛出异常外,有些问题还需要程序员自行抛出异常。我们通过在方法体中使用throw来抛出异常,后面必须try-catch处理异常或用throws声明抛出的异常。
public void setSex(String sex) throws Exception{
if(sex.equals("男") || sex.equals("女")){
this.sex = sex;
}else{
throw new Exception("性别只能为男女!")
}
}
throw 和throws的区别
我们可以看到,抛出异常的时候使用到了throw和throws,两个单词长得差不多,但却是不一样的,throw是用来手动抛出异常的,throws是用来声明抛出的异常的。
throw | throws |
---|---|
生成并抛出异常 | 声明方法内抛出了异常 |
位于方法体内部,可作为单独语句使用 | 必须跟在方法参数列表后面,不能单独使用 |
抛出一个异常对象,且只能是一个 | 声明抛出异常类型,可以跟多个异常,通过逗号隔开 |
异常体系结构
图中可以看到,Object为所有类的父类,Throwable为异常中最高层的父类,它的子类有Error和Exception两种。
Error类异常是仅靠程序本身无法恢复的严重错误,通常是需要去终止程序的。比如内存不足,jvm溢出等。
Exception类异常是由Java应用程序抛出和处理的非严重错误,这类异常是可以处理修复的,通常是希望处理掉异常而不去终止程序。
而Exception类中又可以划分为两类异常:
1、Checked异常。检查时异常,在编译的时候不通过,程序必须处理该类异常,不处理就无法运行。
2、运行时异常。是编译的时候系统不去检查,运行时才会报错。在写代码的时候不要求必须进行try-catch处理或者throws声明抛出。RuntimeException是运行时异常类,它下面有很多子类异常平时也很常见,如ArithmeticException类、NullPointerException类、NumberFormatException类等等。
自定义异常
虽然JDK中提供了很多异常类型,但如果JDK中的异常类型不能满足程序的需要时,可以自定义异常类。
使用自定义异常类的步骤:
1、定义异常类
可以继承Throwable类、Exception类、RuntimeException类中的一种,通常不需要编译时检查的就继承RuntimeException类。
2、编写构造方法,继承父类的实现
3、实例化自定义异常对象
4、使用throw抛出
如果是运行时异常类型的异常,throw抛出异常后,不要求必须throws声明抛出的异常也能运行,但会在运行中报错。
public class PersonException extends Exception{
public PersonException() {
super();
}
public PersonException(String message) {
super(message);
}
}
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws PersonException{
if(age<0 || age>100) {
throw new PersonException("年龄必须在1到100之间!");
}
this.age = age;
}
}
异常链
A方法调用B方法时,B方法却抛出了异常。那么A方法继续抛出原有的异常还是抛出一个新的异常呢?
(1)抛出原有的异常
A方法与B方法进行了关联,不便于代码修改和扩展
(2)抛出新的异常
会丢失原有异常信息
而异常链创建了新的异常却保留了原有异常的信息。
异常处理原则
异常处理与性能的原则:
1、异常只能用于非正常情况
2、不要将过于庞大的代码块放在try中(不好定位异常)
3、在catch中指定具体的异常类型(尽量不用Exception父类)
4、需要对捕获的异常做处理(往外throw会影响性能)