1 异常概述
1.1 什么是异常
简单来说异常就是用来表示Java程序运行过程中的错误(信息)
网络中断, 用户输入信息, 读取不存在的文件
1.2 异常体系与分类
在Java中Throwable作为所有错误跟异常的祖先类
根据错误的严重程度分
- Error: 比较严重的错误(代码处理不了, Jvm内部资源耗尽的错误)
- java.lang.StackOverflowError 栈溢出
- java.lang.OutOfMemoryError 堆溢出
- Exception: 错误程度小, 能够用代码进行处理
- java.lang.ArithmeticException: / by zero 算数异常 /0
- java.lang.NullPointerException 空指针异常
- java.lang.ArrayIndexOutOfBoundsException 数组越界
根据处理方式的不同
- 编译时异常 : 编译不通过(除了RuntimeException及其子类外的其他的异常)
- 运行时异常: 编译通过, 但是运行时可能会出错(RuntimeException及其子类)
Exception是运行时异常还是编译时异常
- Exception是编译时异常和运行时异常的父类
- 在自定义异常的时候,Exception作为编译时异常
1.3 常见的异常
总结一下常见异常:
Error:
java.lang.StackOverflowError 栈溢出
java.lang.OutOfMemoryError 堆溢出
Exception
编译时异常:
java.lang.CloneNotSupportedException 不支持克隆异常
java.io.FileNotFoundException
java.io.IOException
运行时异常:
java.lang.ArithmeticException 算数异常
java.lang.NullPointerException 空指针异常
java.lang.ArrayIndexOutOfBoundsException 数组越界异常
java.lang.ClassCastException 类型转换异常
java.util.InputMismatchException 输入不匹配异常
示例:
public class Demo {
public static void main(String[] args) {
// Scanner scanner = new Scanner(System.in);
// System.out.println("请输入一个数字:");
// int i = scanner.nextInt();
// java.lang.OutOfMemoryError
// int[] arr = new int[2014 * 1024 * 1024];
// System.out.println(1024*1024*1024*1024);
// java.lang.StackOverflowError
// func();
// java.lang.CloneNotSupportedException
Demo demo = new Demo();
// demo.clone();
// java.lang.ArithmeticException: / by zero
// System.out.println(10/0);
//java.lang.NullPointerException
demo = null;
// System.out.println(demo.toString());
// java.lang.ArrayIndexOutOfBoundsException: 1
String[] strs = {"a"};
// System.out.println(strs[1]);
// java.lang.ClassCastException
Father father = new Father();
// Son son = ((Son) father);
}
public static void func(){
func();
}
}
class Father{
}
class Son extends Father{
}
2 异常处理
2.1 JVM默认处理机制
jvm默认异常处理流程
- 当我们代码在执行到,发生错误的地方。
- 一旦发生错误,jvm就会终止我们自己程序的运行,转而执行jvm自己的错误处理流程
- 在发生错误地方,收集错误信息,产生一个描述错误的对象
- 访问收集到的错误信息,将错误信息,输出到控制台窗口中(哪个线程,异常类型名, 异常原因, 哪个类哪个方法哪一行报错了)
执行过程
- 如果错误产生在main方法中
- 当我们的代码执行到错误行数之前,代码是正常执行的
- 当我们的代码执行到错误行数时,JVM会终止程序的执行,抛出一个该异常信息封装成的对象
- 将该对象中的异常信息,打印到控制台上,告诉程序员发生了什么问题
- 发生错误之后的语句,都不执行了
- 如果错误产生在main方法当中的另一个方法中
- 当程序执行到该方法的错误行数时,JVM会终止程序的执行
- 向上给方法的调用者抛出一个该异常信息封装成的对象
- 一直向上抛出,直到抛给main方法,main方法最终抛给JVM
- 发生异常之前的语句正常执行,但是之后的语句都不执行了
- 当程序执行到该方法的错误行数时,JVM会终止程序的执行
- 默认处理机制仅针对运行时异常
/*
*
* Exception in thread "main" java.lang.ArithmeticException: / by zero
at _01code._01exception._02handle._01default.Demo.test(Demo.java:26)
at _01code._01exception._02handle._01default.Demo.main(Demo.java:20)
异常发生的线程:main
异常的类型(全类名):java.lang.ArithmeticException
原因:/ by zero
具体位置:哪个类 哪个方法 哪行代码
*/
public class Demo {
public static void main(String[] args) {
System.out.println("main start");
test();
System.out.println("main end");
}
private static void test() {
System.out.println("start");
System.out.println(10/0);
System.out.println("end");
}
}
2.2 捕获异常,自己处理
2.2.1 try-catch
2.2.1.1 单分支
语法:
方式一:
try{
// 可能出现异常的代码
}catch(异常类型 对象名){
// 对异常的处理操作
}
方式二:
try{
// 可能出现异常的代码
}catch(异常类型1 | 异常类型2 | 异常类型3 | 对象名){
// 对异常的处理操作
}
try-catch的执行:
- 如果try中代码运行时发生了错误,jvm在发生错误的代码处,收集错误信息
- try 块中在错误代码之后的代码,就不会在运行,jvm会跳转到相应的错误处理器中,执行开发者自己写的,错误处理代码
- 错误处理器中的代码,一旦执行完毕紧接着,程序继续正常执行,执行的是整个try代码块之后的代码
注意:catch代码块中的代码,只有try块中的代码执行出错时,才会执行!
/*
* 方式一:
try{
// 可能出现异常的代码
}catch(异常类型 对象名){
// 对异常的处理操作
}
方式二:
try{
// 可能出现异常的代码
}catch(异常类型1 | 异常类型2 | 异常类型3 | 对象名){
// 对异常的处理操作
}
*/
public class Demo {
public static void main(String[] args) {
//想要在try外部访问的值,就要定义在try外部
boolean flag = true;
//快捷键 Ctrl + Alt + t
try {
// 定义一个局部变量 有作用域
// boolean flag = true;
// 可能出现异常的代码
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (ArithmeticException | NullPointerException | ArrayIndexOutOfBoundsException e) {
//获取异常信息,返回字符串
// getMessage() 原因
System.out.println(e.getMessage());
// toString() 类型+ 原因
System.out.println(e.toString());
e.printStackTrace();
// 对异常的处理操作 catch中的语句想要执行 必须匹配成功,才能进入
// System.out.println("捕获到了异常");
// Cannot resolve symbol 'flag'
flag = false;
}
System.out.println("main end");
}
}
2.2.1.2 多分支
语法:
try{
// 可能出现异常的代码
}catch(异常类型 对象名){
// 对异常的处理操作
}catch(异常类型 对象名){
// 对异常的处理操作
}catch(异常类型 对象名){
// 对异常的处理操作
}.....
匹配规则:
1.根据实际的异常对象的类型和异常分支(异常处理器)声明的异常类型,从上到下一次做类型匹配
2. 一旦通过类型匹配,发现实际异常对象的类型和Catch分支(异常处理器)声明的异常类型匹配,就把异常对象交给这个异常分支(异常处理器)
3. 多分支的异常处理的执行,有点类似于多分支if-else的执行,一次匹配,只会执行多个catch分支中的一个
注意事项:
如果说,在多catch分支的情况下,如果不同的catch分支,处理的异常类型,有父子关系那么就一定要注意,处理子类的异常分支写在前面,父类的异常分支写在后面
/*
try{
// 可能出现异常的代码
}catch(异常类型 对象名){
// 对异常的处理操作
}catch(异常类型 对象名){
// 对异常的处理操作
}catch(异常类型 对象名){
// 对异常的处理操作
}.....
*/
public class Demo4 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (NullPointerException e) {
System.out.println("捕获到了空指针异常");
} catch (ArithmeticException e) {
System.out.println("捕获到了算数异常");
} catch (ArrayIndexOutOfBoundsException e){
System.out.println("捕获到了数组越界异常");
} catch (RuntimeException e){
// 如果多分支的异常类型中有父子关系 父类要放在最下面
System.out.println(".....");
}
System.out.println("main end");
}
}
2.3 抛出异常,上层处理
2.3.1 throws关键字
在方法定义时使用,声明该方法可能抛出的异常
对于编译时异常,可以在语法层面强制方法调用者处理该异常
基本语法:
修饰符 返回值 方法名(形参列表) throws 异常列表 {}
解释说明:
- 异常列表: 异常类型1, 异常类型2, … 用逗号隔开,列表中的异常不要出现父子关系,如果有,那么编译器只会强制处理父类
- 只是声明可能抛出,到底抛不抛,看代码
- throws+运行时异常没有意义,因为运行时异常会自动抛出,不需要声明.throws+编译时异常才有意义,这实际上是编译异常处理的一种方式
- 在方法中声明throws+编译时异常,声明可能抛出编译时异常,该方法被调用时就要处理这个编译异常
- 处理编译时异常
- 方法内部try-catch
- throws向上抛,如果在main中就别抛了,处理一下
子类重写父类方法注意:
- 子类方法不能比父类抛出更多的编译时异常
- 父类如果抛出Exception,那么子类就可以随便抛出
- 建议子类重写的时候保持跟父类一样的异常列表
/*
* throws表示抛出异常的可能性
* 使用在方法声明处 形参列表的后面 throws + 异常列表(使用,隔开)
*/
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
// func1();
// Unhandled exception: java.lang.CloneNotSupportedException
try {
func2();
} catch (CloneNotSupportedException e) {
System.out.println("捕获到克隆异常");
}
}
//throws+编译时异常
private static void func2()throws CloneNotSupportedException{
Demo demo = new Demo();
demo.clone();
}
//throws+运行时异常--》没有太大意义
// 异常列表中有父子关系的话 强制处理父类型
private static void func1() throws RuntimeException,NullPointerException,ArithmeticException,ArrayIndexOutOfBoundsException{
System.out.println("func1() start");
System.out.println((10 / 0));
System.out.println("func1() end");
}
}
/*
子类重写父类方法的时候 不能比父类抛出更多的编译时异常
*/
class Father{
public void m1() throws CloneNotSupportedException{
}
public void m2() throws Exception{
}
public void m3() throws ArithmeticException{
}
}
class Son extends Father{
//@Override
//public void m1() throws Exception {
//
//}
@Override
public void m1() throws CloneNotSupportedException {
}
@Override
public void m2() throws CloneNotSupportedException {
}
@Override
public void m3(){
}
}
2.3.2 throw关键字
在方法体中使用
主动在程序中抛出异常
每次只能抛出确定的某个异常对象
基本语法:throw 异常对象 (new 出来的)
注意:
- throw+编译时异常,需要结合throws关键字
// 在方法体中使用
// 主动在程序中抛出异常
// 每次只能抛出确定的某个异常对象
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
func1();
func2();
}
// throw+编译时异常
private static void func2() throws CloneNotSupportedException {
throw new CloneNotSupportedException("抛出一个克隆异常");
}
private static void func1() {
// throw +异常对象
throw new RuntimeException("抛出一个运行时异常");
}
}
2.3.3 throws VS throw
throws
- 用在方法声明后面,跟的是异常类名
- 可以跟多个异常类名,用逗号隔开
- 表示抛出异常,由该方法的调用者来处理
- throws表示出现异常的一种可能性,并不一定会发生这些异常
throw
- 用在方法体内,跟的是异常对象名
- 只能抛出一个异常对象
- 表示抛出异常,可以由方法体内的语句处理
- throw则是抛出了异常,执行throw则一定抛出了某种异常
2.4 异常策略选择
总结一下,目前为止,我们学习过的异常处理策略有2种:
- 捕获并处理try-catch
- 向上抛出
- 运行时异常,自动抛出,直道抛给jvm
- 编译时异常,需要结合throws关键字向上抛
如何选择策略?
- 对于运行时异常,我们不应该写出产生这种异常的代码,应该在代码的测试阶段修正代码。
- 对于编译时异常,功能内部能够处理的就处理,如果不能够或者没有必要处理,就抛出。
2.5 finally
2.5.1 finally
特点
被finally控制的语句体一定会执行
特殊情况:在执行到finally之前jvm退出了(比如System.exit(0))
作用
用于释放资源,在IO流操作和数据库操作中会见到
如何使用
跟try-catch结合
语法:方式一
try{
}catch(){
}catch(){
}.....
finally{
// 一定执行
}
方式二:
try{
}finally{
}
一些奇思妙想
-
try代码块如果有return
- 程序会先执行完finally代码块,回过头执行try中的return
-
catch代码块中如果有return,并且catch正常捕获异常执行
- 程序会先执行完finally代码块后,再回去执行catch中return,从catch代码块中结束方法
-
finally代码中有return
- 不会影响finally代码块执行
-
如果finally和catch中都有return
- 程序会直接从finally代码块中的return结束方法
-
如果try中的异常不能正常捕获,但是finally中有return
- 注意此时程序会跳过这个异常,不会抛出异常给JVM报错
/*
* 1.try代码块如果有return*/
public class Demo2 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println("end");
return;
} catch (Exception e) {
System.out.println("捕获到了异常");
} finally {
System.out.println("一定执行");
}
System.out.println("main end");
}
}
/*
运行结果:
start
end
一定执行*/
/*
* 2.catch代码块中如果有return,并且catch正常捕获异常执行
* */
public class Demo3 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (Exception e) {
System.out.println("捕获到了异常");
return;
} finally {
System.out.println("一定执行");
}
System.out.println("main end");
}
}
/*
* 运行结果:
* start
捕获到了异常
一定执行
*/
// 3.finally代码中有return
public class Demo4 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (ArithmeticException e) {
System.out.println("捕获到了异常");
} finally {
System.out.println("一定执行");
return;
}
// Unreachable statement
// System.out.println("main end");
}
}
/*
运行结果:
start
捕获到了异常
一定执行*/
/*
* 4.如果finally和catch中都有return
* */
public class Demo5 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (ArithmeticException e) {
System.out.println("捕获到了异常");
return;
} finally {
System.out.println("一定执行");
return;
}
// Unreachable statement
// System.out.println("main end");
}
}
/*
运行结果:
start
捕获到了异常
一定执行*/
// 5.如果try中的异常不能正常捕获,但是finally中有return
public class Demo6 {
public static void main(String[] args) {
try {
System.out.println("start");
System.out.println((10 / 0));
System.out.println("end");
} catch (NullPointerException e) {
System.out.println("捕获到了异常");
} finally {
System.out.println("一定执行");
return;
}
// Unreachable statement
// System.out.println("main end");
}
}
/*
运行结果:
start
一定执行*/
2.5.2 final VS finally
final与finally有什么区别
- final关键字,最终的,最后的。可以修饰类 成员变量 成员方法
- 修饰类,该类不能被继承
- 修饰变量表示一个常量
- 修饰方法表示无法重写的方法
- finally代码块,和try…catch一起使用,具有必然执行的特点
- 异常处理体系当中,用于资源释放
2.6 自定义异常
2.6.1 如何自定义异常
2.6.1.1 自定义编译时异常
- 定义一个类继承Exception
- 构造方法
2.6.1.2 自定义运行时异常
- 定义一个类继承RuntimeException
- 构造方法
/*
* 考试成绩必须在0-100分之间,如果有考试成绩不在这个范围之内,则认为成绩异常。
对于以上的异常,Java语言中显然没有一个对应的“考试分数异常超出范围”的异常,因此该异常需要我们自己来定义。
*/
public class Demo{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个分数(0-100):");
int score = scanner.nextInt();
try {
input(score);
} catch (MyException e) {
e.printStackTrace();
}
}
private static void input(int score) throws MyException {
if(score <0 || score > 100){
//抛出异常 throw +异常对象
// throw new MyException2("分数不合法");
throw new MyException("成绩不合法");
}
}
}
class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message);
}
}
class MyException2 extends RuntimeException{
public MyException2() {
}
public MyException2(String message) {
super(message);
}
}