保姆级别的异常类教学(附代码)
目录
1、什么是异常:
2、 java语言中异常以什么形式存在的呢?
3、异常对应的现实生活中是怎样的?
4、异常处理机制
5、异常处理的具体方式
6、运行时异常编写程序时可以不处理
7、方法声明位置上使用throws
8、异常处理的原理
9、异常捕捉和上报的联合使用
10、try......catch深入
11、上报和捕捉怎么选择
12、异常对象的常用方法
13、finally子句的使用
14、try--catch--finally的搭配使用
15、try--finally的搭配使用
16、finally
17、finally的面试题
18、final、finally、finalize 有什么区别?
19、JAVA中自定义异常类
20、异常在实际开发中的作用
1、什么是异常:
当程序运行时发生的不正常的情况,JAVA会通过JVM给控制台打印一段异常信息给程序员
程序员看到异常信息后,可以对程序进行修改,让程序更加健壮
代码演示:
package com.lbj.javase.exceptionTest;public class ExceptionTest02 {public static void main(String[] args) {int a=10;int b=0;//实际上JVM在执行到此处的时候,会new异常对象并且抛出异常,打印输出信息到控制台//Exception in thread "main" java.lang.ArithmeticException: / by zero// at com.lbj.javase.exceptionTest.ExceptionTest02.main(ExceptionTest02.java:14)int c=10/0;System.out.println(c);}
}
2、 java语言中异常以什么形式存在的呢?
异常在java中以类的形式存在,每一个异常类都可以创建异常对象
代码演示:
package com.lbj.javase.exceptionTest;public class ExceptionTest01 {public static void main(String[] args) {NumberFormatException nfe=new NumberFormatException("数字格式化异常");System.out.println(nfe);//java.lang.NumberFormatException: 数字格式化异常 }
}
3、异常对应的现实生活中是怎样的?
举个栗子:
钱包丢了(异常类):
小明钱包丢了(异常对象)
小红钱包丢了(异常对象)
小刚钱包丢了(异常对象)
结论:
类:模板
对象:具体实际存在的个体
4、异常处理机制
Object
Object下有Throwable(可抛出的)
Throwable 下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对异常进行处理,否则编译不通过)
RuntimeException:运行时异常(程序员可以进行处理,也可以不处理让程序照常编译通过即可)
注意:强调!!!所有异常都是发生在运行阶段的
用UML图描述继承结构
UML:统一建模语言,一种图标式语言(画图的)
使用人员:软件架构师、系统分析师、软件设计人员(java开发人员必须要看懂)
作用:描述类和类之间的关系、程序执行的流程、对象的状态等
UML关于异常类的部分结构图:
5、异常处理的具体方式
第一种:在方法声明的位置上,使用throw关键字,抛给上一级(谁调用我,我就抛给谁,抛给上一级)
第二种:使用try...catch语句进行异常的捕捉(这段代码有问题,天知地知你不知我知,因为我捕捉到了)
举个栗子:我是某某集团的一位员工,由于我的失误,导致公司亏损1000元,
“损失的1000元” 可以看做是一个异常发生了,我有两种处理方式
第一种:上报给公司领导,让领导处理
第二种:自己补上这1000元,自己处理
思考:
异常发生后
如果我选择上抛,抛给调用者,调用者需要对这个异常进行处理,那么这个调用者能力足够的话就自己处理,没有能力的话就继续往上抛
如果最后一级实在没办法处理,将终止java程序的执行
员工--》经理--》总经理--》老板
注意:java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM如果知道这个异常发生,只有一个结果,将终止java程序的执行
6、运行时异常编写程序时可以不处理
运行时,意味着程序的异常可以不进行处理,也能编译通过
代码演示:
package com.lbj.javase.exceptionTest;public class ExceptionTest03 {public static void main(String[] args) {/*程序执行到此处发生了Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.lbj.javase.exceptionTest.ExceptionTest03.main(ExceptionTest03.java:12)异常底层new了一个ArithmeticException异常对象然后抛出给main方法,main方法没有处理,将这个异常自动抛给了JVMJVM终止程序执行*/System.out.println(100/0);//这里hello没有执行System.out.println("hello");}
}
7、方法声明位置上使用throws
编译时异常,必须进行处理,否则编译器报错
代码演示:
package com.lbj.javase.exceptionTest;public class ExceptionTest04 {public static void main(String[] args) {//main方法调用doSome方法//我们在调用doSome方法的时候必须对这种异常进行预先的处理//如果不处理,编译器报错doSome();//报错信息//java: 未报告的异常错误java.lang.ClassNotFoundException; 必须对其进行捕获或声明以便抛出}/*doSome方法在方法声明的位置上使用了throws ClassNotFoundException这个代码表示doSome()方法在执行过程中,有可能会出现异常叫做“类没有找到的异常”直接父类是:Exception所以ClassNotFoundException属于编译时异常*/public static void doSome() throws ClassNotFoundException{System.out.println("doSome()!!!");}
}
8、异常处理的原理
第一种处理方式:在方法声明位置继续往上抛(下面这段代码的main方法将异常上抛给了JVM)
上抛类似于推卸责任,继续把异常传递给下一个调用者
package com.lbj.javase.exceptionTest;public class ExceptionTest05 {public static void main(String[] args) throws ClassNotFoundException {doSome();}public static void doSome() throws ClassNotFoundException{System.out.println("doSome()!!!");}
}
第二种处理方式:try...catch进行捕捉
捕捉相当于把异常拦截下,异常真正解决了,调用者并不会知道自己调用的东西有异常
package com.lbj.javase.exceptionTest;public class ExceptionTest05 {public static void main(String[] args) {try {doSome();} catch (ClassNotFoundException e) {e.printStackTrace();}}public static void doSome() throws ClassNotFoundException{System.out.println("doSome()!!!");}
}
9、异常捕捉和上报的联合使用
先搭建一个方法调用的基础结构
package com.lbj.javase.exceptionTest;/*** 处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出* 谁调用我这个方法,我就抛给谁,上抛给调用者来处理*/
public class ExceptionTest06 {public static void main(String[] args) {m1();}private static void m1() {m2();}private static void m2() {m3();}private static void m3() {}
}
当我们进行异常调用上抛的时候
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*** 处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出* 谁调用我这个方法,我就抛给谁,上抛给调用者来处理*/
public class ExceptionTest06 {public static void main(String[] args) throws Exception {System.out.println("main begin");m1();System.out.println("main end");}private static void m1() throws FileNotFoundException, ClassCastException {System.out.println("m1 begin");m2();System.out.println("m1 end");}private static void m2() throws FileNotFoundException {System.out.println("m2 begin");//编译器一开始报错的原因是m3() 方法声明位置上有throws FileNotFoundException//我们在这里调用m3() 的时候没有对异常进行预处理,所以编译报错m3();System.out.println("m2 end");}private static void m3() throws FileNotFoundException {//通过类的继承结构看到FileNotFoundException父类是IOException//IOException的父类是Exception//因此得知是编译时异常new FileInputStream("D:\\2021-2022课件\\123.txt");}
}
注意:
一般不建议在main方法上使用throws,因为这个异常如果真正发生了,一定会抛给JVM,JVM只有终止。
因此,一般main方法中的异常建议使用try...catch进行捕捉,main就不会继续上抛
当以上程序:
new FileInputStream("D:\\2021-2022课件\\123.txt");
是正确的时候,运行结果如下:
当文件IO流不正确的时候,会产生异常信息
new FileInputStream("D:\\2021-2022课件");
以下结果得知:一个方法体中的代码出现异常之后,如果上报的话,此方法结束
我们对代码进行修改,将异常信息在main方法中进行捕捉后:
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*** 处理异常的第一种方式:在方法声明的位置上使用throws关键字抛出* 谁调用我这个方法,我就抛给谁,上抛给调用者来处理*/
public class ExceptionTest06 {public static void main(String[] args){System.out.println("main begin");try {m1();//e 是new FileNotFoundException的对象的内存地址} catch (FileNotFoundException e) {System.out.println("文件不存在,可能路径错误,也可能是文件删除了,文件不属于IO流文件");}System.out.println("main end");}private static void m1() throws FileNotFoundException, ClassCastException {System.out.println("m1 begin");m2();System.out.println("m1 end");}private static void m2() throws FileNotFoundException {System.out.println("m2 begin");//编译器一开始报错的原因是m3() 方法声明位置上有throws FileNotFoundException//我们在这里调用m3() 的时候没有对异常进行预处理,所以编译报错m3();System.out.println("m2 end");}private static void m3() throws FileNotFoundException {//通过类的继承结构看到FileNotFoundException父类是IOException//IOException的父类是Exception//因此得知是编译时异常new FileInputStream("D:\\2021-2022课件");}
}
代码分析:
由于路径是错误的,路径内容并不属于IO流文件
new FileInputStream("D:\\2021-2022课件");
但是此时异常已经被main方法捕捉到,在控制台上打印一段异常的信息,这段信息程序员就可以知道自己的程序有问题,需要修改
运行结果如下:
规律:
捕捉到的异常代码段会完全执行(如main中的begin和end)
只要异常没有被捕捉,采用上报的方式,此方法的后续代码不会执行
另外要注意:try语句块中的某一行出现异常,该行后面的代码不会执行
10、try......catch深入
catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
catch可以写多个,建议catch的时候,一个一个精确处理,方便程序员调试
catch写多个的时候,一定要初一,从上到下,!!!必须遵守从小到大,不能先父类异常再子类异常
JDK8新特性,可以用 | 符号分隔异常catch(FileNotFoundException | NullPointerException e)
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class ExceptionTest07 {public static void main(String[] args) {
// try {
// FileInputStream fis = new FileInputStream("D:\\2021-2022课件");
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// }// try {
// FileInputStream fis = new FileInputStream("D:\\2021-2022课件");
// } catch (Exception e) {//用了多态 父类引用指向子类对象
// System.out.println("文件不存在");
// }try {FileInputStream fis = new FileInputStream("D:\\2021-2022课件");fis.read();} catch (FileNotFoundException e) {e.printStackTrace();}catch (IOException e){System.out.println("读写异常");}}
}
11、上报和捕捉怎么选择
如果希望调用者来处理,选择throws上报
其余情况,皆用try......catch
12、异常对象的常用方法
第一个:获取异常简单的描述信息 String msg = exception.getMassage();
第二个:打印异常追踪的堆栈信息 exception.printStackTrace();
代码演示:
package com.lbj.javase.exceptionTest;public class ExceptionTest08 {public static void main(String[] args) {//这里只是为了测试getMassage()方法和printStackTrace()方法//这里只new了异常对象,但是没有将异常对象抛出。JVM会认为这是一个普通的java对象NullPointerException exception=new NullPointerException("空指针异常");//获取异常简单描述信息,这个信息实际上就是构造方法上面String参数String s=exception.getMessage();System.out.println(s);//打印异常堆栈信息,并不代表此时程序正在发生异常//java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印exception.printStackTrace();System.out.println("hello");}
}
结果演示:
这里飘红的信息并不是异常报错,只是将异常信息进行输出
代码演示:
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;public class ExceptionTest09 {public static void main(String[] args) {try {m1();} catch (FileNotFoundException e) {//在日常开发中,建议使用打印异常堆栈追踪信息,养成好习惯e.printStackTrace();}//如果这里是一段正常的代码,那么上一段代码不会影响到下面的代码,这就是try--catch的好处System.out.println("上面出异常,下面依旧正常");}private static void m1() throws FileNotFoundException {m2();}private static void m2() throws FileNotFoundException {m3();}private static void m3() throws FileNotFoundException {new FileInputStream("D:\\2021-2022课件");}
}
结果演示:
查看上图的异常信息的时候,我们应该怎么看才能快速调试程序呢?
答案:
异常信息追踪信息,从上往下一行一行看
但是要注意的是,SUN写的代码就不用看了(看包名就知道是自己的还是SUN的)
主要问题还是出现在自己编写的代码上
代码演示(获取异常的简单描述信息):
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;public class ExceptionTest10 {public static void main(String[] args) {try {m1();} catch (FileNotFoundException e) {//获取异常的简单描述信息String msg=e.getMessage();System.out.println(msg);}//如果这里是一段正常的代码,那么上一段代码不会影响到下面的代码,这就是try--catch的好处System.out.println("上面出异常,下面依旧正常");}private static void m1() throws FileNotFoundException {m2();}private static void m2() throws FileNotFoundException {m3();}private static void m3() throws FileNotFoundException {new FileInputStream("D:\\2021-2022课件");}
}
结果演示:
13、finally子句的使用
代码演示(请问:这样的异常会发生什么情况):
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class ExceptionTest11 {public static void main(String[] args) {try {//创建输入流对象FileInputStream fis=new FileInputStream("D:\\2021-2022课件\\123.txt");//这里一定会出现空指针异常String s=null;s.toString();//流使用结束后要关闭,因为流式占用资源的fis.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e){e.printStackTrace();}}
}
结果演示(结果:String s=null;出了异常,下面的一句代码fis.close();就不会执行,流就不会关闭):
14、try--catch--finally的搭配使用
代码演示(finally中的代码是一定会执行的):
package com.lbj.javase.exceptionTest;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.exceptionTest* @date 2021/3/23 20:49* @Copyright 公司*/
public class ExceptionTest11 {public static void main(String[] args) {FileInputStream fis = null;try {//创建输入流对象fis=new FileInputStream("D:\\2021-2022课件\\123.txt");//这里一定会出现空指针异常String s=null;s.toString();//流使用结束后要关闭,因为流式占用资源的//以上程序出现异常,流也必须关闭,放在此处可能流关闭不了//fis.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e){e.printStackTrace();} catch(NullPointerException e){e.printStackTrace();} finally {//流的关闭在这里比较保险//finally中的代码是一定会执行的//即使try中出现异常if(fis!=null){ //此判断是为了避免空指针异常而关闭流try {//close()方法有异常,采用捕捉的方式fis.close();} catch (IOException e) {e.printStackTrace();}}}System.out.println("这段话会不会执行呢");}}
演示结果:
15、try--finally的搭配使用
代码演示(再次强调,放在finally语句块中的代码是一定会执行的,除非退出JVM):
package com.lbj.javase.exceptionTest;public class ExceptionTest12 {public static void main(String[] args) {try {System.out.println("123");return;} finally {//finally中的语句能执行到System.out.println("abc");}//return一旦执行,整个方法体结束,卸载return后面的语句也不会执行//System.out.println("faffaf");}
}
结果演示:
123
abc
16、finally
代码演示(退出JVM之后finally语句中的代码就不执行了):
package com.lbj.javase.exceptionTest;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.exceptionTest* @date 2021/3/23 21:28* @Copyright 公司*/
public class ExceptionTest13 {public static void main(String[] args) {try{System.out.println("try...");//退出JVMSystem.exit(0);} finally {System.out.println("finally");}}}
结果演示:
try...
17、finally的面试题
java语法规则,有一些规则是不能被破坏的
方法体中的代码,必须自上而下的顺序逐行执行
return语句还必须保证是最后执行的,一旦执行,方法体结束
面试题目(请分析输出结果):
先执行了finally中的代码,然后再执行return,不冲突
package com.lbj.javase.exceptionTest;public class ExceptionTest14 {public static void main(String[] args) {int m=m();System.out.println(m);}public static int m(){int i=100;try{//return语句还必须保证是最后执行的,一旦执行,方法体结束return i;}finally {i++;}}
}
输出结果:
100
反编译后的效果(需要用反编译工具):
j:其实是临时变量
public static int m(){
int i =100;
int j =i;
i++;
return j;
}
18、final、finally、finalize 有什么区别?
package com.lbj.javase.exceptionTest;public class ExceptionTest15 {public static void main(String[] args) {//final是关键字,赋值后不能再次赋值final int a=1;//a=2;//finally是异常机制中的和try要一起使用//finalize()是Object类中的一个方法,作为方法名出线//是JVM的垃圾回收器负责调用}
}
19、JAVA中自定义异常类
SUN公司提供的JDK内置异常类肯定是不够用的,在实际开发中,有很多业务
这些业务出现异常后,JDK都是没有的,我们通过查看源代码得知,异常类其实是有规律的
得出规律:
第一步:编写一个类继承Exception或者RuntimeException
第二步:提供两个构造方法,一个是无参数的,一个是有参数的
代码演示:
自定义异常类
package com.lbj.javase.exceptionTest;public class MyException01 extends Exception{public MyException01() {}public MyException01(String message) {super(message);}
}
调用自定义异常类
package com.lbj.javase.exceptionTest;public class ExceptionTest16 {public static void main(String[] args) {//创建异常对象(只是new了异常对象,并没有手动抛出)MyException01 e=new MyException01("用户名不能为空");//打印异常堆栈信息e.printStackTrace();//获取异常简单描述信息String msg= e.getMessage();System.out.println(msg);}}
控制台打印信息:
20、异常在实际开发中的作用
(自定义的异常类,可以在对应的类中直接new出来,然后再进行throw手动抛出,再在调用此异常类的方法时,手动try--catch处理,就可以达到用异常信息流打印到控制台,并且终止程序执行,可以替代return这种单纯结束方法体的行为)
代码演示:
步骤一:自定义异常类
package com.lbj.javase.array;public class MyStackOperationException extends Exception{public MyStackOperationException() {}public MyStackOperationException(String message) {super(message);}
}
步骤二:实例化异常类
package com.lbj.javase.array;/**
练习编写程序,使用一维数组,模拟栈数据结构要求:1、这个栈可以存储java中任何引用类型的数据2、在栈中提供push方法模拟压栈(栈满了,要有提示信息)3、在栈中提供pop方法模拟弹栈(栈空了,也要有提示信息)4、编写测试程序,new栈对象,调用push pop 方法来模拟弹栈压栈的动作5、假设栈的默认初始化容量是10(请注意无参构造方法的编写方式)*/
public class ArrayTest10 {private Object[] elements;//需要有一个栈帧,这个栈帧,永远指向栈顶部元素[0]//注意:最初的栈是空的,一个元素也没有//那么这个默认的初始值是多少?//如果index采用0,表示栈帧指向顶部元素的上方。相当于空的指向//如果index采用-1,表示栈帧指向顶部元素private int index;public ArrayTest10(){//假设栈的默认容量是10,那么一维数组动态初始化的固定长度就等于10//因为实例变量初始化的时候是构造方法在执行的时候,所以构造方法中初始化和外边初始化效果是一样的this.elements=new Object[10];//给index赋值this.index=-1;}//压栈方法//obj是被压入的元素public void push(Object obj) throws MyStackOperationException {if(this.index>=this.elements.length-1){
// System.out.println("压栈失败,栈满");
// //如果return语句执行,整个方法就应该结束
// return;//改良//创建异常对象MyStackOperationException e=new MyStackOperationException("压栈失败,栈满");//手动将异常抛出去throw e;}//承接上面的return语句,如果程序能走到这里,说明栈没满this.index++;this.elements[index]=obj;//obj其实是obj.toString 说了很多次 在System.out.println中会自动调用toString方法System.out.println("压栈"+obj+"成功,栈帧指向"+index);}//弹栈的方法,从数组中往外面取元素,每取一个,栈帧减一public Object pop() throws MyStackOperationException {if(this.index<0){
// System.out.println("栈空,弹栈失败");
// return null;//改良throw new MyStackOperationException("栈空,弹栈失败");}//程序执行到此处说明栈没有空System.out.println("弹栈"+elements[index]+"成功,栈帧指向"+index);//栈帧向下移动一位this.index--;return null;}//set和get也许使用不上,但是你必须写上,这是规矩//封装:第一步 属性私有化 第二步 对外提供get和set方法public Object[] getElements() {return elements;}public void setElements(Object[] elements) {this.elements = elements;}public int getIndex() {return index;}public void setIndex(int index) {this.index = index;}
}
步骤三:最终调用类的引用对异常进行try--catch处理
package com.lbj.javase.array;/*** @author LBJ* @version V1.0* @Package com.lbj.javase.array* @date 2021/2/21 16:41* @Copyright 公司*/
public class MyStackTest01 {public static void main(String[] args) {//new 是把构造方法实例化//也就是意味着 创建一个栈对象,初始化容量是10个ArrayTest10 arrayTest10=new ArrayTest10();//调用方法压栈try {arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());arrayTest10.push(new Object());//最后压入的,最先弹出来//压到这个元素的时候失败了arrayTest10.push(new Object());} catch (MyStackOperationException e) {//输出简单的异常信息System.out.println(e.getMessage());}//调用方法弹栈//弹栈顾名思义我们需要获取些什么//Object ele=arrayTest10.pop();try {arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();arrayTest10.pop();//弹出的时候,此时栈空了arrayTest10.pop();} catch (MyStackOperationException e) {//输出简单的异常信息System.out.println(e.getMessage());}}
}
运行结果:
编程习惯:
当if判断为程序执行到此就是“错误”的时候,不要用return来结束程序,我们要用异常来结束程序
下一章节,我们讲异常的练习题