异常
概述
程序运行过程出错的情况就是异常。Java提供了异常处理机制,出现不正常情况的时候,就会把异常信息打印到控制台,供程序员参考修改。
这是Java打印的异常信息,这个信息是JVM打印的。
异常的作用: 增强程序的健壮性。
异常的存在形式
异常在Java中是以类和对象的形式存在,可以通过异常类创建异常对象!
在JVM执行到错误行的时候,就会创建一个异常对象,并且会将该异常对象抛出,打印到控制台。
如果有多个同类的异常出现,会创建多个异常对象。
异常的继承结构
UML和startUML
UML是一种图标式的统一建模语言,不是只在Java中使用,只要在面向对象编程的语言里,都有UML。
在UML图中,可以描述类和类之间的关系,程序执行的流程,对象的状态等……
用这类语言的工具(startUML)可以画图帮助理解Java中的关系。
继承结构图
通过UML,我们可以清晰地了解异常的继承结构:
之后的继承结构图都会采用这种方式呈现~
编译时异常和运行时异常的区别
所有异常都是在运行阶段发生的,因为只有运行的时候才能创建对象,编译阶段是无法创建对象的。
编译时异常 | 发生概率较高,在运行之前就需要对其进行预处理。 |
运行时异常 | 发生概率较低,对这类异常进行预处理会很麻烦 |
如果不这么划分,程序需要对所有异常都进行预处理,效率太低了!
两种异常处理方式
- 在方法声明的位置上,使用
throws
关键字,将异常上抛。
注意事项:
- Java中异常发生后如果一直上抛,最终抛给了main方法,main方法继续上抛给了JVM,JVM知道这个异常就会直接终止Java程序的执行!
- 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,但是可以更少!
- 使用
try{}catch(){}
进行异常捕捉。
异常处理的原理
public class test01 {public static void main(String[] args) {doSome();}public void doSome() throws ClassNotFoundException{System.out.println("emm");}
}
public void doSome() throws ClassNotFoundException{}
方法使用了编译时异常的异常上抛的情况就需要在main方法的编写阶段对该异常做预处理!
在main方法使用
try{}catch(){}
处理而不是继续上抛!
此时,我们可以继续上抛或者使用try{}catch(){}
处理。
public static void main(String[] args) {try{doSome();}catch(ClassNotFoundException e){e.orintStackTrace();}
}
这种方法在这里就已经将异常拦截了,上面不会知道有这个错误!
出现异常,采用异常上抛,异常后面的不会执行,整个方法就结束了;采用try{}catch(){}
处理,try异常后面的代码不会执行,整个异常处理外的代码正常执行。
try{}catch(){}
深入理解
- catch后面小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型(多态)。
- catch可以写多个,建议写catch的时候一个异常一个catch处理,这样有利于程序的调试。
- catch写多个时,如果多个异常存在继承关系,顺序必须从子到父依次写;如果没有继承关系,catch的顺序没有要求。
JDK8.0以后,可以采用或者的方式将多个异常放在一个catch中:
catch(ClassNotFoundException|ArithmeticException|NullPointerException e){}
两种异常处理方式的选择
如果希望调用者来处理调用函数的异常,就选择异常上抛,其他情况就使用try{}catch(){}
。
异常对象的常用方法
获取异常简单的描述信息 | String msg = exception.getMessage(); |
打印异常堆栈的追踪信息 | exception.printStackException(); |
public static void main(String[] args) {NullPointerException e = new NullPointerException("空指针异常");String msg = e.getMessage();System.out.println(msg);e.printStackTrace(); System.out.println("helloWorld!");
}
这里并没有发生异常,只是将异常对象的信息抛出,并不会结束程序!异常追踪信息从上往下看,SUN公司的代码就不用看了。
结果中helloWorld先打印,是因为Java打印异常堆栈信息的时候,采用异步线程的方式打印的,涉及多线程,后面再学~
finally子句
概述
在finally子句中的代码是最后执行的,并且一定会执行,哪怕try语句中出现异常!
finally子句必须和try语句一起出现,不能单独出现,但是可以只跟try不跟catch。
使用场景: 完成资源的关闭/释放。
退出JVMfinally的执行情况
public static void main(String[] args) {try{System.out.println("helloWorld!");System.exit(0);}finally{System.out.println("helloWorld!");}
}
很明显,当JVM关闭的时候,finally语句也不再执行!
自定义异常
实际开发的时候,有很多异常是JDK内置中不存在的,此时我们就需要自定义异常。
Java中自定义异常分为两步:
- 编写一个类继承Exception或RuntimeException。
- 提供两个构造方法,一个无参,一个有参(带有String的)。
public class MyException extends Exception{public MyException(){ }public MyException(String s){super(s);}
}
之后我们就可以通过异常对象的方法来调用这个类,此时我们就完成了我们自定义的异常!