一、异常
(一)异常的概述
1、在Java运行过程中,出现了不正常的情况,称为异常
2、异常就是一个对象,描述那些和正常情况不相符的异常情况,包含了异常情况的原因、详细信息、异常类型、错误位置等,这些内容都被封装在异常对象里
3、异常也是一种处理异常情况的机制,可以对异常对象进行捕获、或者使程序发生跳转甚至停止运行
(二)异常的体系
1、Throwable:可抛出的,是异常体系和错误体系的总父类,所有的异常或者是错误对象都是Throwable的子类类型。只有Throwable的体系内的类型,才能进行异常处理。
2、Error:错误,是Throwable的子类,用于描述那些无法捕获和处理的错误情况,属于非常严重的错误:StackOverFlowError
3、Exception:异常,是Throwable的子类,用于描述那些可以被捕获和处理的例外情况,属于不太严重的错误:NullPointerException
4、Exception是Java中所有异常类型的总父类,只要是异常类型,都是它的子类
5、RuntimeException:运行时异常,是Exception类的子类。RuntimeException及其子类都是运行时异常,除此外,所有的异常都是编译时异常。所谓运行时异常,就是在编译阶段不做检查;所谓编译时异常,就是在编译阶段,会额外的做检查。
(三)JVM默认处理异常机制
1、在代码的某个位置,出现了和正常情况不同的情况,就将异常信息封装到一个异常对象中
2、将异常对象抛给调用该方法的方法
3、某个方法接收到其他方法抛来的异常对象,也没有办法处理,会继续向上抛,最终抛给主方法,主方法也没有办法处理,将异常对象抛给虚拟机
4、JVM是我们手动调用的,JVM会将异常对象的所有信息,通过错误流打印出来,并且结束JVM的运行
5、总结:JVM默认处理异常的方式:一层一层向上抛,JVM接收到之后打印异常信息并结束自己
(四)手动处理异常的方式
1、有两大类的处理异常的方式:
(1)异常的声明:某个方法存在编译时异常,编译期就无法通过,需要在出现编译时异常的方法声明上,对可能会发生的异常类型进行声明
(2)异常的捕获:出现异常之后,可以通过某些代码格式对异常对象进行捕获和处理,此时,程序出现的异常对象,就不会再向上抛,而是被捕获下来,我们做出相应的处理后,出现异常代码之后的代码,会继续运行。我们可以自己定义处理异常的逻辑。
2、捕获处理异常的格式:
(1)try…catch
(2)try…catch…catch…catch…
(3)try…catch…finally
(4)try…finally
(五)try…catch格式
1、try,关键字,含义:试一试,可能出现问题的代码放在try模块中
2、catch,关键字,含义:捕获,当真的出现了异常时,问题代码会抛出一个异常对象,可以通过catch模块将异常对象捕获下来,就不会按照JVM默认机制一层一层向上抛了,并且还能在catch模块中对异常对象进行处理
3、格式:
try {
可能出现异常的代码
} catch(可能出现异常的类型 标识符) {
出现这种异常后的处理方式
}
4、运行机制:
(1)try之前的代码正常运行
(2)运行try中的代码
(3)如果try中代码没问题,则catch不执行,直接执行try…catch之后的代码
(4)如果try中代码有问题,抛出异常对象,则catch尝试捕获
(5)如果catch声明的异常类型能匹配异常对象的类型,则捕获进行处理,try…catch之后的代码正常执行
(6)如果catch声明的异常类型不能匹配异常对象的类型,则无法捕获,按照JVM默认机制处理,try…catch之后的代码不执行
(7)额外的,try中如果某句代码抛出异常对象,try出现异常代码之后的代码,不会再执行,会直接进catch捕获
(8)捕获到异常对象后的处理方式:记录日志、反复运行、鸵鸟政策等
public static void main(String[] args) {
System.out.println("很快啊");
try {
int i = 10 / 0;//new ArithmeticException();
System.out.println("传统功夫点到为止");
System.out.println(i);
} catch (ArithmeticException ae) {//ArithmeticException ae = new ArithmeticException();
}
System.out.println("后面那三个年轻人不讲武德");
}
(六)try多catch
1、在一段代码中,可能会出现多种异常(虽然可能出现多种异常,但是实际发生的时候,只会抛出多种类型异常的其中一种类型的对象),所以要准备多种异常处理机制
2、格式:
try {
可能会出现异常的代码
} catch(异常类型1 标识符) {
处理异常的方式1
} catch(异常类型2 标识符) {
处理异常的方式2
} … {
} catch(异常类型n 标识符) {
处理异常的方式n
}
3、执行流程:
(1)执行try中代码,如果没有异常,catch就不执行,直接执行catch之后的代码
(2)如果try中某句代码发生了异常,try中这句代码之后的代码就不会再去执行了,而是直接跳转到后面的catch模块
(3)当try中抛出异常对象,会按照从上到下的顺序,匹配每一个catch模块,哪一个catch匹配到了,就执行哪一个处理方式,处理完毕之后,再去执行try…catch之后的代码
4、注意事项:
(1)如果是多catch的情况,一般子类类型异常的声明写在上面,父类类型异常的声明写在下面,因为当异常对象抛出时,按照从上到下的顺序匹配catch,如果父类在上,写在下面的子类就永远无法被匹配
(2)在JDK7之后,可以对异常类型进行逻辑运算,实际上就是使用【|】来表示多种异常类型,可以有相同的处理方式,还能够结合多catch
catch(异常类型1 | 异常类型2 | 异常类型3 标识符) {
以上异常类型的共同处理方式
} catch (异常类型4 | 异常类型5 标识符) {
处理手段
}
(七)try…catch…finally
1、finally,关键字,含义:最后。当代码中有必须要去执行的代码,可以放在finally表示的模块中
2、格式:
try {
可能出现异常的代码
} catch(可能出现异常的类型 标识符) {
出现这种异常后的处理方式
} finally {
一定会被执行的代码
}
3、finally模块的作用:
(1)如果try中代码出现了异常,就会抛出异常对象,之后直接尝试用catch进行捕获,万一没有捕获到,就会按照JVM默认机制处理,后续的一些代码就不会被执行。如果某些代码一定要执行,就可以将这些代码放入finally模块中,即便是catch捕获不到异常对象,按照JVM默认方式处理,在finally模块中的代码也一定会被执行
(2)作用:一般用于关闭资源
(八)try…finally
格式:
try {
可能出现异常的代码
} finally {
一定会被执行的代码
}
3、作用:
(1)这种格式无法捕获异常,目的就是在于一旦出现异常,按照默认机制处理,只是在关停虚拟机之前,需要关闭一些资源等
(2)假设如果有两句代码都要执行,两句都可能出现异常,那么将一句代码写在try中,另一句写现在finally中,第一句出现问题,也会保证第二句也能执行
(九)编译时异常和运行时异常
1、无论是编译时异常还是运行时异常,真正发生的时候都在运行期。
2、区别:
(1)继承的角度:Exception除了RuntimeException之外,它本身其子类都是编译时异常;RuntimeException及其子类都是运行时异常
(2)代码表现角度:1)编译时异常:会在代码编译期进行语法检测,一旦有编译时异常,在编译期是不会通过编译的,必须通过异常处理的两种手段之一去处理:异常的声明,异常的捕获。当然,在运行阶段,编译时异常也不一定会发生。
2)运行时异常:在编译阶段没有任何提示,运行阶段不发生就不发生了,如果发生会直接抛出异常对象
(十)继承体系中的常用方法
1、异常体系中,绝大部分异常类型只有构造,没有成员方法,所有的成员方法都在父类Throwable中,目的是为了使用统一的手段处理不同的异常对象
2、构造方法:
(1)Throwable() 创建异常对象,异常对象中的消息消息为null
(2)Throwable(String message)
创建异常对象,异常对象中具有指定的详细信息
(3)Throwable(Throw cause) 创建一个带有异常原因的异常对象
(4)Throwable(String message, Throw cause) 创建一个带有详细信息和异常原因的异常对象
3、成员方法:
(1)getCause() 获取异常对象中的异常原因
(2)getMessage() 获取异常对象的详细信息
(3)toString() 打印异常的简短描述
(4)printStackTrace() 打印栈轨迹
(十一)throw关键字
1、throw,关键字,含义:抛出。用于抛出一个异常对象
2、程序在运行过程中,发生异常的时候抛出的是一个实实在在的异常对象。我们在实际应用中,如果代码中哪里有和生活不相符的情况,可以将异常信息等封装为一个异常对象,通过throw关键字将异常对象抛出
3、作用:创建一个异常对象,使用throw关键字抛出,实现了程序的跳转甚至结束
4、说明:当代码正常运行的时候,运行时异常不会发生,就相当于没有任何异常对象;当代码运行过程中出现了异常情况,才会创建异常对象并且抛出。
public class Demo07_Throw {
public static void main(String[] args) {
Person p = new Person();
new Person("zhangsan", -1);
try {
System.out.println("!!!!!!!!!!!!");
p.setAge(-20);//输入-20调用异常抛出
System.out.println(p.getAge());
} catch (RuntimeException e) {
//e.printStackTrace();
//p.setAge(18);
System.out.println(e.getMessage());
}
System.out.println(p.getAge());
}
}
*********************************************************************************************************************************************************
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
if (age >= 0 && age <= 130) {
this.age = age;
} else {
throw new RuntimeException("年龄输入不合法");
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0 && age <= 130) {
this.age = age;
} else {
//System.out.println("年龄输入不合法");
/*RuntimeException re = new RuntimeException("年龄输入不合法");
throw re;*/
throw new RuntimeException("年龄输入不合法");
}
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
(十二)throws关键字
1、throws,关键字,含义:抛出**。用于声明一个或者多个异常类型**
2、在某个方法中,如果存在一些编译时异常,并且这些编译时异常还尚未处理,就会报错阻止编译,我们就必须要对编译时异常进行处理:捕获或者声明。如果需要声明编译时异常,就需要在方法的声明上,参数列表之后,跟上关键字throws,并且注明声明一个或者几个异常类型。
3、格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, 异常类型3… {
方法体
}
4、注意事项:
(1)如果发生的是编译时异常,那么既可以进行捕获处理,也可以进行声明处理。声明处理只是具有提示作用,提示此处可能会出现哪些类型的异常,如果将来运行真的出了问题,还是会抛出一个实实在在的异常对象
(2)如果发生的是一个运行时异常,不需要声明处理,最多只需要捕获处理,也可以选择不处理。但是当运行时真正发生了运行时异常,会真的抛出一个异常对象
(3)A方法如果声明了编译时异常,那么B方法调用A方法也会有编译时异常,此时B方法就必须要针对编译时异常做出处理,要么声明,要么try…catch。如果B方法进行了try…catch,那么B方法的调用者C方法就不会再有编译时异常了,如果B方法进行了异常的声明,那么C方法要继续考虑处理编译时异常的情况。
(4)声明异常的原则:尽量声明少的、声明小的异常类型
(十三)throw和throws关键字的比较
1、throw是对已经出现的异常对象进行抛出,throws关键字是对可能发生的异常类型进行声明
2、throw是对异常对象实实在在的抛出,一旦使用了throw关键字,就一定会有一个异常对象出现;throws关键字是对多个可能出现的异常类型的声明,即使声明了,也有可能一个都不出现
3、throw后面跟一个异常对象;throws后面跟多个异常类型
4、throws声明异常类型的时候,声明尽量少尽量小的异常类型
(十四)方法重写的补充说明
1、在子父类中,方法名称相同,参数列表相同,与返回值类型有关
2、有关:父类方法返回值是A类,子类重写的方法返回值是A或者A的子类
3、私有方法不能被重写
4、重写的方法权限可以保持不变,或者越来越大,不能越来越小
5、如果父类方法声明了A类型的异常,子类重写的方法只能声明A或者A的子类类型的异常
6、如果父类方法没有声明异常,子类重写的方法有编译时异常,只能进行try…catch,不能throws
(十五)自定义异常
1、异常报错时,异常类型就能见名知意知道是什么类型的问题,但是由于JDK中提供的异常类型有限,不能很好地覆盖我们开发中出现的各个类型的问题,所以需要我们自定义异常类型,以精确表示出现的异常
2、自定义异常的步骤:
(1)创建一个异常类型,类名做到见名知意
(2)需要让自定义类型加入异常体系
1)如果自定义的是运行时异常,就继承RuntimeException
2)如果自定义的是编译时异常,就继承Exception
(3)因为所有异常为了方便统一处理,都会使用Throwable中的方法,并且为了以后方便创建异常对象和处理异常对象,我们需要在自定义异常类型中,根据父类构造方法的情况,提供相应构造
public class Demo10_TestDefiendException {
public static void main(String[] args) {
Police p = new Police();
try {
p.setAge(-10);
} catch (IllegalAgeException e) {
e.printStackTrace();
}
}
}
public class IllegalAgeException extends Exception {
public IllegalAgeException() {
super();
}
public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public IllegalAgeException(String message, Throwable cause) {
super(message, cause);
}
public IllegalAgeException(String message) {
super(message);
}
public IllegalAgeException(Throwable cause) {
super(cause);
}
}
public class Police {
private String name;
private int age;
public Police() {
super();
}
public Police(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) throws IllegalAgeException {
if (age >= 0 && age <= 120) {
this.age = age;
} else {
throw new IllegalAgeException("年龄非法,请输入[0,120]之内的年龄");
}
}
@Override
public String toString() {
return "Police [name=" + name + ", age=" + age + "]";
}
}