文章目录
- 1、异常
- 2、类Throwable
- 3、运行时异常和编译时异常
- 4、异常的处理
- 5、异常导致某些代码不能被执行
- 6、try...catch总结
- 7、异常对象的常用方法
- 8、try...catch和finally
- 9、final、finally和finalize的整理区分
- 10、如何自定义异常
- 11、异常在实际开发中的作用
- 12、异常与方法覆盖
程序执行过程中发生了不正常的情况,JVM将异常信息打印到控制台,从而方便调试和增强程序的健壮性。
1、异常
异常在Java中以类的形式存在,如NullPointerException,每一个异常类都可以创建异常对象。
NumberFormatException nfe = new NumberFormatException("数字格式化异常");
NullPointerException npe = new NullPointerException("空指针异常");
举个例子:
int a = 10;
int b = 0;
int c = a/b;
//当JVM执行到这个地方时,会new异常对象,即new ArithmeticException("/by zero");
//并且JVM将new的异常对象抛出,打印输出信息到控制台
类比于生活:
火灾是一种异常(一种抽象出来的类),而异常对象则是xxx地方发生火灾了。
2、类Throwable
父类 java.lang.Object<-------java.lang.Throwable类------>子类Error和Exception
插播:
UML,即Unified Modeling Language,统一建模语言。
用来描述类个类之间的关系,程序执行的流程,对象的状态等。
对应的工具有:Rational Rose,startUML
3、运行时异常和编译时异常
定义
- 所有Exception类的直接子类,称编译时异常(并不是字面意思上的编译阶段发生的)该类异常,必须在编写程序时,预先对这种异常进行处理,不处理,则编译时抛错
- RuntimeException及其所有的子类,称为运行时异常,该类异常在编写程序时,可处理,也可不处理
异常的发生就是new对象,而只有运行阶段可以new对象,故运行时异常和编译时异常都是发生在运行阶段。
两种异常的区别
- 编译时异常发生的概率较高,又称受检异常、受控异常。类比生活中的:出门前看到外面正在下大雨,可预料,不打伞出门大概率生病,生病这个”异常“发生的概率很大,所以出门前要做”处理“拿伞。
- 运行时异常发生的概率比较低,又称非受检异常、非受控异常。类比生活中:走在大街上,被天上掉下来的陨石砸中了,概率极低,没必要提前做出预处理。若处理,你活着就很累,相对应的,代码就很本末倒置。
4、异常的处理
处理思路1:
在调用者方法声明的位置上,使用throws关键字,抛给上一级,即谁调用,抛给谁
public class Exception1 {
public static void main(String[] args) throws ClassNotFoundException {
//这里main方法调用doSome方法,必须对这个异常进行预处理
doSome();
}
//doSome方法的声明位置有throws ClassNotFoundException
//这表示这个方法执过程中很有可能会出现ClassNotFoundException异常
//ClassNotFoundException异常的直接父类是Exception,即它是编译时异常
public static void doSome() throws ClassNotFoundException{
}
}
😉
在Java中,异常发生之后如果一直上抛,最终抛给main方法,main方法继续向上抛到JVM,到JVM,程序会被终止执行。
处理思路2:
使用try……catch语句进行异常的捕捉
public class Exception1 {
public static void main(String[] args) {
try {
doSome();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void doSome() throws ClassNotFoundException{
}
}
try{ //尝试
m1();
}catch(FileNotFoundException e){ //e是一个引用,保存了new出来的那个异常对象的内存地址
System.out.println("文件不存在"); //捕捉到异常以后执行的语句
}
对于运行时异常,如ArithmeticException,可以处理,也可以不处理
try{
System.out.println(100/0);
catch(ArithmeticException e){
System.out.println("算术异常");
}
总结:
- throws上抛类似于推卸责任,继续吧异常传递给调用者,让调用者来决策怎么处理(继续抛还是捕捉)
- try…catch捕捉等于把异常拦下解决了,调用者是不知道的
几点注意事项:
- 在方法的定义中,+throws 异常类名,该异常类名可以是该异常类的父类,也可以是异常类名,异常类名1,异常类名2,异常类名3
- 不建议在main方法上使用throws,因为异常若真的发生了,一定抛给JVM,则JVM会终止程序执行。而异常机制的作用和初衷就是增强程序的健壮性(即要做到异常发生了也不影响程序的执行)
- main方法中用try…catch ,别上抛了。
5、异常导致某些代码不能被执行
总结:
- 只要异常没有被捕捉,即采用了上报的方式,则当某行代码出现异常的时候,此方法结束,方法中的后续代码不会被执行,相当于return。再往下分析,就只看其的上抛(调用者)就好。
- try语句块中,某一行出现异常后,该行后面的代码不会执行,catch捕捉异常后,catch后的代码继续执行。
6、try…catch总结
😉 catch后,写确切的类型、写其父类型、父父类型也可以
try{
FileInputStream fis = new FileInputStream("D:\\not_exist.java");
//这里写的是FileNotFoundException的父类IO Exception
}catch(IO Exception e){
System.out.println("文件不存在");
}
😉 catch可以写多个,当这些异常父类一样时,直接写一个他们的父类也行,但建议精确catch,方便调试
try{
...
//出现异常时,从上往下匹配catch
}catch(xx e){
...
}catch(xxx e){
...
}catch(xxxx e){
...
}
😉 catch写多个的时候,从上到下,必须遵循从小到大,若父类异常在上,则catch匹配时,父类下的精确子类一定轮不上执行,会报错。
😉 JDK8中,允许使用或"|"来连接多个异常
try{
...
}catch(FileNotFoundException|ArithmeticException|NullPointerException e){
...
}
上抛和捕捉如何选择?
------
如果希望调用者去处理,则用throws抛给调用者,反之,自己处理则try…catch
7、异常对象的常用方法
Exception exception = new Exception();
1)getMessage()方法
//作用:获取异常的简单描述信息
String msg = exception.getMessage();
2)printStackTrace()方法
//作用:打印异常追踪的堆栈信息
exception.printStackTrace();
举例:
public class Exception2 {
public static void main (String[] args){
//异常的发生即new异常对象,那这行代码会导致程序终止运行吗?
//不会,这里只是new了一个异常对象,但并没有将异常对象throws抛出
//因此JVM会认为这是一个普通的Java对象
NullPointerException e = new NullPointerException("发生空指针异常");
String msg = e.getMessage();
//getMessage方法的输出,其实是用有参构造方法传入的那个String
System.out.println(msg);
e.printStackTrace();
System.out.println("Hello World!");
}
}
运行结果:
多次运行可以看到,Hello World一会儿在堆栈信息之上,有时在堆栈信息之下,这是因为Java打印异常堆栈信息追踪信息的时候,采用的是异步线程的方式。
Tip:
printStackTrace打印堆栈信息的结果也为红色,不是编译器出错了,如果是出错了,那下面的代码是不会执行的。错误分析时,从上往下看异常,下面的异常通常是上面的导致的。此外注意看包名,sun公司包后面的信息可以适当跳过。
8、try…catch和finally
- 😉 在finally子句中的代码是最后执行的,并且一定会执行,即使try语句块中的代码出现了异常
- 😉 finally子句必须和try一起出现
- 😉 通常在finally语句块中完成资源的释放/关闭,以保障资源一定被关闭
- 😉 也可无catch,直接try+finally
- 😉 退出JVM时,finally语句不执行
将上面try语句块中的return;改为:
System.exit(0);
//再运行,则只输出try,finally中的语句不再执行
整理一个finally的坑,或许面试用得上:
public class Exception2 {
public static void main (String[] args){
int result = m();
System.out.println(result); //100
}
public static int m(){
int i = 100;
try{
return i;
}finally{
i++;
}
}
}
-----
//按照刚学的执行顺序,应该是先i++,再return,但这里是特殊
//使用反编译工具可以看到m()方法其实是这样:
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
9、final、finally和finalize的整理区分
- final是一个关键字,表示最终的,其修饰的类无法继承、修饰的方法无法覆盖、修饰的变量不能重新赋值
- finally是一个关键字,和try一起出现,finally语句块中的代码必须执行(除非中途退出JVM)
- finalize是Object类中的方法名(即标识符),该方法由GC机制调用
10、如何自定义异常
SUN提供的JDK内置异常不够用,实际的开发中有的异常和业务挂钩,这种异常需要我们自己定义,自定义异常的步骤是:
- 编写一个类继承Exception(编译时异常)或者继承RuntimeException(运行时异常)
- 提供两个构造方法,一个无参数的,一个带有String参数的
public class MyException extends Exception{
public MyException(){
}
public MyException(String s){
super(s);
}
}
11、异常在实际开发中的作用
以下以数组模拟压栈弹栈为改良例子:
//之前的栈满是通过return+println来表达异常
public void push(Object obj){
if(this.index >= this.elements.length-1){
System.out.println("压栈失败,栈满");
return;
}
this.index++;
this.elements[index] = obj;
System.out.println("压栈"+obj+"成功,栈帧指向"+ index);
}
栈满是一种异常,我们用异常机制来改良:
/**
* 改良后的压栈方法
*/
public void push2(Object obj) throws MyStackOperationException {
if(index >= elements.length-1){
//创建异常对象,并将其抛出
MyStackOperationException e = new MyStackOperationException("栈满");
throw e;
//抛出异常后,因为这个自定义异常我写的是编译时异常,故要预处理
//这里选择throws,我定义异常的目的就是把栈满的异常信息传递出去
//自己定义再自己捕捉,一切都没意义了,所以在方法名定义的末尾加上throws
}
this.index++;
this.elements[index] = obj;
System.out.println("压栈"+obj+"成功,栈帧指向"+ index);
}
throw new MyStackOperationException("栈满");
用这种抛异常来代替return + println
注意throw和throws的区别:
- throws:在方法声明的位置上使用,表示上报异常信息给调用者
- throw:手动抛出异常
12、异常与方法覆盖
覆盖那一章中提到:重写之后的方法,不能比重写之前的方法抛出更多或者更宽泛的异常,可以相等或者更少
class Animal{
public void doSome(){
}
public void doOther() throws Exception{
}
}
class Cat extends Animal{
//抛的更多了,error
public void doSome throws Exception{
}
//抛出更少,√
public void doOther(){
}
//抛出一样的,√
public void doOther2() throws Exception{
}
//抛出范围更小的,√
public void doOther() throws NullPointerException{
}
//抛出运行时异常,√
public void doOther() throws RuntimeException{
}
}