程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常,那么异常发生之后怎么办,Java提供了更加优秀的解决办法-异常处理机制。异常处理机制能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。
- 异常分类
- 两种异常处理方式
- 异常被吃掉了
- 注意点
异常分类
根据发生原因可分为三类:
(1)用户输入了非法数据
(2)要打开的文件不存在
(3)网络通信时连接中断,或者JVM内存溢出
根据类型可分为三类:
(1)检查性异常:用户错误或问题引起的异常,无法预见的。例如要打开一个不存在文件时,一个异常就发生了
(2)运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略
(3)错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略
异常体系结构
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类,在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception,如下图所示:
java中定义的异常(Exception)和错误(Error)都继承自Throwable类。其中错误的产生大多是由于运行环境jvm导致的,这部分错误我们通过程序很难纠正,而Java的异常分为两种,checked异常(编译时异常)和Runtime异常(运行时异常)
编译时异常:checked异常都是可以再编译阶段被处理的异常,所以它强制程序处理所有的checked异常,java程序必须显式处理checked异常,如果程序没有处理,发生错误,无法通过编译
运行时异常:Runtime异常无须处理也可以通过编译。所有的Runtime异常原则上都可以通过纠正代码来避免
异常处理的几个关键字:
关键字 | 说明 |
---|---|
try | 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出 |
catch | 用于捕获异常。catch用来捕获try语句块中发生的异常 |
finally | finally语句块总是会被执行。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止 |
throw | 用于抛出异常 |
throws | 用在方法签名中,用于声明该方法可能抛出的异常 |
既然说java的异常都是一些异常类的对象,那么这些异常类也有一些方法我们应该了解:
方法 | 说明 |
---|---|
getMessage() | 返回该异常的详细描述字符 |
printStackTrace() | 将该异常的跟踪栈信息输出到标准错误输出(异常链) |
printStackTrace() | 将该异常的跟踪栈信息输出到指定的输出流 |
getStackTrace | 返回该异常的跟踪栈信息 |
两种异常处理方式
1. try…catch捕获异常
java提出了一种假设,如果try中的语句一切正常那么将不执行catch语句块,如果try中语句出现异常,则会抛出异常对象,由catch语句块根据自己的类型进行捕获。若没有相应的catch块,则抛出。
所以其执行步骤可以总结为以下两点:
(1) 如果执行try块中的业务逻辑代码时出现异常,系统自动生成一个异常对象,该异常对象被提交给java运行环境,这个过程称为抛出(throw)异常。
(2) 当java运行环境收到异常对象时,会寻找能处理该异常对象的catch块,如果找到合适的cathc块并把该异常对象交给catch块处理,那这个过程称为捕获(catch)异常;如果java运行时环境找不到捕获异常的catch块,则运行时环境终止,java程序也将退出。
private void tryCatchWay() {
FileInputStream fis=null;
try {
fis=new FileInputStream("a.txt");
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
// return语句强制方法返回
return;
// 使用exit来退出虚拟机
//System.exit(1);
}finally{
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
fis=null;
}
System.out.println("fis资源已被回收");
}
}
执行结果如下:
I/System.out: a.txt (No such file or directory)
fis资源已被回收
如果使用exit而不是return,那么将会得到如下结果:
I/System.out: a.txt (No such file or directory)
以上两种情况显示:除非在try块或者catch块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。
当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。
如果有finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用再跳回去执行try块、catch块里的任何代码了。所以,一般情况下,不要在finally块中使用return或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。
以下四种情况将会导致finally块不执行:
(1)在finally语句块中发生了异常
(2)在前面的代码中使用了System.exit()退出虚拟机
(3)程序所在线程死亡
(4)关闭cpu
重点: 如果catch块或finally块中没有throw语句或者return语句,那么try…catch之后的语句就一定会执行,因为这个异常已经被正常捕获了。
2. throw(s)处理异常
如果当前出现的异常在本方法中无法处理,我们只能抛出异常。 如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理(异常没有在本地处理,逻辑上throw之后的程序不会在进行)。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块(这属于异常没有得到处理,将终止出现异常的线程),将按照下面的步骤处理:
(1)调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
(2)如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
关于throw的用法我们有几点注意事项要注意:
重点: throw语句后不允许有紧跟其他语句,因为这些没有机会执行(块外也不行,因为不会执行,无论是否被调用方捕获。如果异常是在本方法内部throw直接捕获,那是可以执行块后面的代码的,记住只要throw异常,throw之后的代码都不会在执行),以一段程序来说明这个问题:
private void throwWay() {
caexc();
}
public static void exc() throws ArithmeticException{
int a =1;
int b=4;
for (int i=-2;i<3;i++){
a=4/i;
System.out.println("i="+i);
}
}
public static void caexc(){
try {
exc();
} catch (ArithmeticException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
执行结果如下:
I/System.out: i=-2
i=-1
W/System.err: java.lang.ArithmeticException: divide by zero
at com.tt.test.phototest.activity.ExceptionActivity.exc(ExceptionActivity.java:88)
at com.tt.test.phototest.activity.ExceptionActivity.caexc(ExceptionActivity.java:94)
可以看到当 i 为0的时候抛出了异常,同时程序也不再往下执行,中断并打印异常信息
异常被吃掉了
正常的情况是我们在一个方法中抛出异常,在调用他的方法中捕获异常,并且异常的类型是相同的才可以正确捕获,例如两个都是IO异常,抛出和捕获的异常的类型不同也是不能正常捕获的,例如throw出的是IO异常,而在调用他的方法中捕获的是空指针异常,是不能正常捕获的,及时调用方捕获了也是属于java的原生异常捕获。最典型的吃掉异常就是在catch中不做任何处理,这样肯定是不行的,需要对捕获的异常进行正确处理,最简单的就是打印堆栈信息,下面是一种比较好的处理方式,可以参考:
map.put("status", 500);
try{
//代码省略
map.put("message", "success!");
map.put("status", 200);
} catch (UnknownHostException e) {
//域名错误
map.put("message", "请输入正确的网址");
LoggerUtils.fmtError(e, "网址异常[%s]", url);
} catch (SocketTimeoutException e) {
//超时
map.put("message", "请求地址超时");
LoggerUtils.fmtError(e, "请求地址超时[%s]", url);
} catch (Exception e) {
//其他异常
map.put("message", "请求出现未知异常,请重试!\r\n" + e.getMessage());
LoggerUtils.fmtError(e, "请求出现未知异常,请重试![%s]", url);
}
return map;
或者我们继续将异常抛出也可以:
try{
//代码省略
} catch (UnknownHostException e) {
throw new HNException("xxx处理失败。",e);
}
注意点
(1)catch块尽量保持一个块捕获一类异常,不要忽略捕获的异常,捕获到后要么处理,要么重新抛出新类型的异常
(2)不要用try…catch参与控制程序流程,异常控制的根本目的是处理程序的非正常情况
(3)避免过大的try块,不要把不会出现异常的代码放到try块里面,尽量保持一个try块对应一个或多个异常
(4)细化异常的类型,不要不管什么类型的异常都写成Excetpion
(5)不要捕获unchecked Exception
(6)finally块不能抛出异常,如果抛出,外部捕捉到的异常就是finally块中的异常信息,而try,catch块中发生的真正的异常堆栈信息则丢失了
(7)抛出自定义异常异常时带上原始异常信息
(8)不要使用同时使用异常机制和返回值来进行异常处理
尊重作者,尊重原创,参考文章:
https://blog.csdn.net/zx64881926/article/details/52300271
https://blog.csdn.net/csdnliuxin123524/article/details/78657118
https://blog.csdn.net/jakezhang1990/article/details/72880700
https://www.cnblogs.com/liuwt0911/articles/3730438.html
https://www.jianshu.com/p/49d2c3975c56
https://www.sojson.com/blog/251.html