Java异常处理
程序运行时发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程 序自生自灭,立刻退出终止。
Scanner sc=new Scanner(System.in);
String ss=sc.nextLine();
int kk=Integer.parseInt(ss);
System.out.println("用户输入数据为:"+kk);
//用户输入abc
/*则执行结果为:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
atjava.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at test.A.main(A.java:9) */
意外产生和处理过程概述
运行时有许多因素引起出错,硬件失败,除法溢出,数组下标越界。
出错的位置都在方法Method里
出错后方法生成一个Exception对象,并把它交给JVM。这个对象里包括许多信息:错误类型,错误位置。JVM负责处理Exception对象
这种生成Exception对象并交给系统的过程叫抛出意外throwing an exception
一个方法抛出意外后,JVM就试图在“调用栈”里找能处理这个类型Exception对象的方法。找到就执行,找不到程序中止
出错的处理方式
以前正常流程代码和问题处理代码相结合。现在将正常流程代码和问题处理代码分离,以提高阅读性。
public static int bbb(int k) {
if (k > 100) {
//输出正常处理
return 0;
} else if (k > 10) {
return -3;
} else if (k > 0) {
return -2;
} else {
return -1;
}
}
//可以根据返回值判断程序的执行情况,如果>=0表示正常执行完毕,如果<0,则返回不同的负值表示不同的运行 情况。调用方法时可以根据不同的返回值进行不同的处理
//java中引入异常对象的目的在于给调用方更加详细的出错说明,以达到异常处理和正常代码分离,并且按照不同 的异常进行处理的目的
异常
Java异常是Java提供的用于处理程序中错误的一种机制
- 所谓错误是指在程序运行的过程中发生的一些异常事件。如除0溢出、数组下标越界、所需要读取的文件不存在
- 设计良好的程序应该在异常发生时提供处理这些错误,使得程序不会因为异常的发生而阻断或者产生不可预见的结果
- Java程序的执行过程中如果出现异常事件,可以生成一个异常类对象,该异常对象封装了异常事件的信息并经被提交给Java运行时系统,这个过程称为抛出异常throw
- 当Java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这个过程称为捕获异常
Java异常处理的关键字有throws、throw、try、catch、Õnally共5个关键字
异常用途
异常就是在程序运行时由代码所产生的不正常状态。换句话说,异常就是一个运行时不支持异常处理的计算机语言中,错误必须被人工进行检查和处理,这显然麻烦而低效
Java语言提供了异常处理机制,为方法的异常终止和出错处理提供了清楚的接口
1、用来在发生运行异常时告诉程序如何控制自身的运行,以防止错误的进一步恶化
2、每个方法必须对他可能抛出的异常进行预先声明,在定义方法时,必须声明这个方法可能会抛出哪一种或几种异常
异常的分类
JavaSE中定义了很多异常类,这些类可以对应各种各样可能出现的异常事件。
int k=0;
System.out.println(100/k); System.out.println("end...");
允许则会发现出现报错ArithmeticException,同时显示出错行号,但是看不到end...的输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cJElP6O-1613612034717)(C:\Users\Lenovo\AppData\Local\Temp\1611229573780.png)]
Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别
Java异常可以分为3大类
所有异常对象的父类为Throwable—Object
1、Error及其子类:错误,一般指的是虚拟机的错误,是由Java虚拟机生成并抛出,程序不能进行处理所以也不加处理,例如OutOfMemoryError内存溢出、调用栈溢出StackOverFlowError
public static void pp(){
System.out.println("this is pp....");
pp();
}
在控制台上显示Exception in thread main java.lang.StackOverflowError
2、RuntimeException及其子类:运行时异常(非受检型异常),是由于编程bug所导致,希望越早发现越好,所以不进行处理,直接中断报错即可,编程人员针对报错信息修改程序bug来解决问题。
面试题:请说出至少5种常见的异常类型,用于检查应试者平常的编码量
常见的第一种运行时异常:ArithmeticException算术异常,就是执行数学计算时产生的非正常情况,如除以0
int k=0;
System.out.println(10/k);
这里明显是一个编程的bug,因为除法计算要求分母非0。修正bug,这里进行分母为0的处理
int k = 0;
if (k != 0)
System.out.println(10 / k);
else
System.out.println("除数为0,不能执行除法计算"); System.out.println("end.....");
常见的 第二种运行时异常:NullPointerException空指针异常(试图访问null对象的引用)
Object obj=null;
System.out.println(obj.toString()); //注意如果直接输出反而没有异常System.out.println(obj)
这里出错的原因是调用对象的方法之前没有进行非空判断,所以NullPointerException;
Object obj = null;
if (obj != null)
System.out.println(obj.hashCode());
else
System.out.println("obj没有具体对象");
常见的 第三种运行时异常:IndexOutOfBoundsException下标出界
int[] arr=new int[3]; //异常的原因是访问数组的下标超出允许的范围[0,arr.length]
System.out.println(arr[3]); //ArrayIndexOutOfBoundsException
//异常的原因是通过下标访问字符串中字符时,超出了下标允许的范围[0,str.length()]
String str="abcd"; System.out.println(str.charAt(5));//StringIndexOutOfBoundsException
解决方法是通过下标访问时应该判定下标的取值是否合法
int[] arr = new int[] { 1, 2, 3, 4 };
int index = 0;
if (index >= 0 && index < arr.length) System.out.println(arr[index]);// 避免ArrayIndexOutOfBoundsException
String str = "asdfasdf";
if (index >= 0 && index < str.length()) System.out.println(str.charAt(index));// 避免StringIndexOutOfBoundsException
常见的 第四种运行时异常:ClassCastException试图把一个对象转换成非法类型
Object dd=new Date();
//异常的原因是dd是Date类型,这个类型和Random没有任何关系,所以不能进行强制类型转换
Random rr=(Random)dd; //ClassCastException
//这里不会异常,因为学生是人的子类,所以任何一个学生都是人类
Object dd=new 学生();
人 obj=(人)dd; //但是这样操作也会出问题。异常的原因是:人是学生类的父类,不是每个人都是学生,所以不能进行强制类型转 换
Object dd=new 人();
学生 s1=(学生)dd;//ClassCastException
解决方案:进行类型强制转换之前,应该对变量类型进行判断,如果合法才进行类型的转换
if(obj!=null && obj instanceof Random){
Random r=(Random)dd;
}
常见的第五种运行时异常:NumberFormatException数据格式异常,一般出现在数据类型转换中
String ss = "123.456";// 字符串,其中内容为浮点数
/*
* 数据类型转换有2种写法:
* Integer.valueOf("内容"):Integer
* Integer.parseInt("内容"):int
*/
Integer kk = Integer.valueOf(ss);// NumberFormatException数据的格式错误,数据是浮点数格式,不 是整数
int k2 = Integer.parseInt(ss);
int k3=Integer.valueOf(ss);//自动装拆箱操作支持
进行数据类型转换之前可以进行格式验证
String ss = "123456";// 字符串,其中内容为浮点数 // 因为没有正则式
boolean bb = true; for (int i = 0; i < ss.length(); i++) {
char cc = ss.charAt(i); //这里没有考虑+-号,如果考虑还需要处理+-两个符号
if (cc < '0' || cc > '9') {
bb = false;
break;
}
}
if(bb){
int kk=Integer.valueOf(ss); System.out.println(kk);
}else{
System.out.println("数据格式错误!");
}
这里进行数据的合法性验证后再进行数据类型转换比较繁琐,所以最终的解决方案是使用try/catch语法结果,针对出现问题后进行处理
int kk=0;
try{
kk=Integer.parseInt(s1); //如果没有问题则进行数据类型转换
}catch(NumberFormatException e){
kk=100;//如果数据不合法,则使用这个默认值
}
System.out.println(kk);
3、Exception及其子类中除了RuntimeException及其子类之外的其它异常:受检型异常(非运行时异常),这类异常属于明知道可能出现,但是没有办法杜绝的异常。这类异常一般采用try/catch或者throws声明抛出的方式进行异常处理,当程序出现了非正常情况,尽量保证程序正常结果,而不是立即中断
- 受检型异常:明知道存在这种异常的可能,但是没有办法杜绝,所以必须进行编码异常处理
- 非受检型异常:这种异常不需要进行处理,发现的越早越好
命令行上的数据录入
异常的捕获和处理
语法规则:
try{
try代码段中包含可能产生异常的代码,有人称为陷阱代码,在执行过程中如果出现了异常,则异常之后的 java语句不会执行。转而执行catch部分的代码
} catch(SomeException e){
可以写多个try后可以跟一个多个catch代码段,针对不同异常执行不同的处理逻辑。当异常发生时,程序会中止当前的 流程,根据获取异常的类型去执行响应的代码段。注意异常类型判定时是从上向下逐个判断的。
} finally{
finally代码是无论是否发生异常都会执行的代码
}
Integer kk=null;
try{
String str="123.456"; //由于str参数数据不合法,不是整数,所以这里异常NumberFormatException
kk=Integer.parseInt(str);
System.out.println("数据转换结束");//上句出现异常,这里就不会执行
} catch(Exception e){//出现异常后进行类型判断,如果是这种异常则执行这里的代码段,可以有多个捕捉异常
System.out.println("出现了错误:"+e.getMessage()); //e.getMessage():String用于获取异常的 提示信息
e.printStackTrace(); //在控制台(标准错误流System.err)上输出异常调用栈
}
System.out.println("转换结果为:"+kk);
注意:
- try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用
- Java采用的是终结式异常处理机制,java中异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行, 它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
- try{}后面必须跟一个Õnally或者catch,其中有个特殊写法可以省略后面的finally和catch
Java异常处理
try–用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被 抛出。 catch–用于捕获异常。catch用来捕获try语句块中发生的异常。finally–finally语句块总是会被执行。它主 要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会执行 try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳 回执行,直接停止。 throw-- 人为编程实现抛出异常。
throws–用在方法签名中,用于声明该方法可能抛出的异常,要求谁调用谁处理这个异常。主方法上也可以使用throws抛出。如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。
try语句
try语句指定一段代码,该段代码就是一次捕获并处理异常的范围
在执行过程中,该段代码可能会产生并抛出一种或几种类型的异常对象,它后面的catch语句要分别对这些异常做响应的处理
如果没有异常产生,所有的catch代码段都能略过不执行
Integer kk=null;
try{String str="123.456";
//由于str参数数据不合法,不是整数,所以这里异常NumberFormatException
kk=Integer.parseInt(str);
System.out.println("数据转换结果");//如果上句出现异常,则立即会中断代码执行,这里不会有执行 机会
} catch(NumberFormatException e){
//异常处理中catch只匹配一次,匹配之后的后续catch会被忽略。注意这里不是最佳匹配
System.out.println("数据格式错误!");
} catch(Exception e){
System.out.println("出现了错误:"+e.getMessage()); }
System.out.println("准换结果为:"+kk);
catch语句
在catch语句块中是对异常进行处理的代码,每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象,同时允许异常的继续抛出
catch只匹配成功一次即可,注意不是最佳匹配,例如交换Exception和NumberFormatException的位置,这里就 会语法报错,报错的提示信息为Unreachable catch block for NumberFormatException. It is already handled by the catch block for Exception,含义是catch(NumberFormatException e)是不可达语句, NumberFormatException是Exception的子类。
强调:在编写多个catch时,小异常一定在大异常之前
Integer kk=null;
try{
String str="123.456";
kk=Integer.parseInt(str);
System.out.println("数据转换结束");
} catch(NumberFormatException e){ System.out.println("数据格式错误!"); //继续抛出异常,不再是直接消费处理掉了,导致的结果是try之后的代码并没有得到执行 throw new Exception(e.getMessage()); //throw e;在用在方法上throws声明抛出,因为NumberFormatException是运行时异常 //throw new Exception(""); 需要在方法上声明抛出,因为Exception是受检型异常
} catch(Exception e){ System.out.println("出现了错误:"+e.getMessage());
}
System.out.println("转换结果为"+kk);
常见的3种输出异常的用法
- System.out.println(e);//java.lang.ArithmeticException: / by zero
- System.out.println(e.getMessage()); // / by zero
- e.printStackTrace(); 调用栈
JDK1.7引入多异常捕获
大部分情况下可能会出现多种不同异常但是处理方法一致时,注意这多个异常类型之间没有继承关系,如果有继承关系则应该写成try{}catch(父类型e){}即可
try{
int k=0;
system.out.println(10/k);
} catch(ArithmeticException e){ System.out.println(e); }catch(NullPointerException e){ System.out.println(e);
}
两次捕捉异常的处理是一样的,就可以将两个异常卸载一个catch中,其中多个异常类型之间使用|分割
try{
int k=0;
system.out.println(10/k);
} catch(ArithmeticException | NullPointerException e){//含义和上面的一致 System.out.println(e);
}
try{} catch(IndexOutOfBoundException | NumberFormatException | ArithmeticException e){ }。这里需要注意的是捕获一种异常时,异常对象没有final的含义,但是多种异常时隐含了final含义,所以不能修改对象引用
try{
int k=0;
system.out.println(10/k);
} catch(ArithmeticException e){ System.out.println(e);
e=new ArithmeticException("asd"); //注意按照Java变量的规则,这里的类型不能变,所 以可以new当前类型,也可以子类型
throw e;
}catch(NullPointerException e){ System.out.println(e);
}
但是使用多异常时则不能修改e对象
try{
int k=0;
System.out.println(10/k); }catch(ArithmeticException | NullPinterException e){ 要求异常类型之间不能有继承关系,如果有继承 关系应该只写大异常类型即可
System.out.println(e);
e=new ArithmeticException("asd"); throw e; }
最后需要记得catch可以写多个,但是有顺序,需要时先小后大,如果先大后小,则小代码不可及
正确写法:
try{
int k=0;
System.out.println(10/k); }catch(ArithmeticException | NullPointerException e){ System.out.println("小异常:"+e);
}catch(Exception e){ System.out.println("大异常:"+e);
}
错误写法: 因为匹配规则是顺序匹配,不是最佳匹配
try{
int k=0;
System.out.println(10/k);
} catch(Exception e){
System.out.println("大异常:"+e);
} catch(ArithmeticException | NullPointerException e){//报错
System.out.println("小异常:"+e);
}
finally语句
finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理
无论try所指定的程序块是否抛出异常,finally所指定的代码都要执行。
try可以没有catch而直接使用finally,当然在方法上声明抛出
Scanner sc=new Scanner(System.in); System.out.println("请输入一个整数");
String ss=sc.nextLine();
try {
int kk=Integer.parseInt(ss.trim()); System.out.println("输入数据正确,具体值为:"+kk);
} catch(NumberFormatException e){
System.out.println("输入的数据格式错误!");
} catch (Exception e) {
System.out.println("其它错误:"+e.getMessage());
} finally{ //不管陷阱代码中是否出现异常,是否执行了catch语句,finally语句块一定要执行
System.out.println("finally....");
}
System.out.println("end pp....");//这个语句有可能不执行,但是finally是无论如何都会被执行。典型 案例去掉所有的catch语句,当出现异常时finally还是执行了的,但是这个语句没有执行
通常在finally语句中可以进行资源的清除工作,例如关闭打开的文件,删除临时文件等。注意Java的垃圾回收机制只能回收再堆内存中对象所占用的内存,而不会回收任何物理资源(如数据库连接、网络连接和磁盘文件等)
特殊情况导致finally执行出现问题
- 在前边的代码中使用System.exit(int)退出应用
System.exit(int status)这个方法是用来结束当前正在运行中的java虚拟机。
- status是非零参数,那么表示是非正常退出。
- System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序。
try{
int k=10;
System.out.println(10/k);
System.exit(0); //执行这里表示无条件立即终止执行
} finally{
System.out.println("finally....");
}
- 程序所在的线程死亡或者cpu关闭
- 如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束
Scanner sc=new Scanner(System.in); System.out.println("请输入一个整数");
String ss=sc.nextLine();
try {
int kk=Integer.parseInt(ss.trim()); System.out.println("输入数据正确,具体值为:"+kk); System.exit(0);
} finally{
int kk=0; System.out.println(10/kk); //这里出现异常,提供提示为ArithmeticException, 而不是上面出现的NumberFormatException
System.out.println("finally....");
}
考核点:finally块和return
public static int pp(){
try{
int i=1; System.out.println(10/i); System.out.println("try....");
return 0; //由于有finally语句,所以这里的return并不会立即返回,而是等待finally执行后才 能返回
} catch(Exception e){
System.out.println("catch...");
return 1;
} finally {
System.out.println("finally...");
return 2; //这里的return会替换前面的return结果
}
}
public static void main(String[] args){
int res=pp();
System.out.println("Main:"+res);
}
返回值不管是否异常都是finally中的return 2
finally、final和finalize的区别
JDK1.7+引入的自动关闭的写法
JDK1.7引入了自动关闭资源的try语句块,在后续模块中演示
try(FileInputStream fis=new FileInputStream(“a.txt”)){}在方法上声明抛出异常即可,没有finally语句块
这里引入了Closeable和AutoCloseable接口,可以自动关闭资源,
异常的捕获和处理
Java的异常处理机制使得异常事件能够沿着被调用的顺序向前寻找,只要找到符合该异常种类的异常处理程序,就会进行异常处理逻辑的执行,并且被消费掉,不会继续查找。
容易出现的一个编码问题:隐藏异常
异常处理允许嵌套,但是一般不建议嵌套层数过多
最佳异常相关编程实践
1、不要在fianlly中使用return。
public static int pp() {
int res = 0;
try {
int i = 0; res = (10 / i); System.out.println("try....");
} catch (Exception e) {
res = -1;
} finally {
res = 100;
}
return res;
}
2、不要在finally中抛出异常。
3、减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
4、将尽量将所有的return写在函数的最后面,而不是try … catch … finally中
自定义异常
1、通过继承java.lang.Exception类【受检型异常】或者RuntimeException【非受检型异常】声明自定义异常类 【也允许继承Throwable或者Exception的子类】。例如因为业务导致可能出现的问题账户余额不足
public class AccountException extends Exception { public AccountException() {
super("账户余额不足"); //这里的参数是默认的报错信息
} public AccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public AccountException(String message, Throwable cause) {
super(message, cause);
} public AccountException(String message) { super(message);
}public AccountException(Throwable cause) { super(cause);
}
}
异常处理建议规则
1、不要过度使用异常,不要把异常当做不同的逻辑分支对待
2、不要使用过于庞大的try块
3、避免使用catch all语句 try{}catch(Throwable t){}
eSuppression, writableStackTrace);
}
public AccountException(String message, Throwable cause) {
super(message, cause);
} public AccountException(String message) { super(message);
}public AccountException(Throwable cause) { super(cause);
}
}
**异常处理建议规则
1、不要过度使用异常,不要把异常当做不同的逻辑分支对待
2、不要使用过于庞大的try块
3、避免使用catch all语句 try{}catch(Throwable t){}
4、坚决不要忽略捕获的异,坚决不允许catch块中内容为空