前言
直白点说:就是代码出现意外状况。影响到程序的运行。 其实,在我们接触代码开始,就一直在接触异常,只是从来没有分类。 这点在java中,更加明显。 现在我们就来基本了解一下异常。
?
复现以前接触过的异常
算术异常
?
空指针异常
?
数组 索引/下标 越界异常
?
?
输入的数据类型不匹配的异常
 
编译期 错误 / 异常 和 运行时 错误 / 异常
细心的人会注意到,有些错误,在程序还没运行时,编译器就给你报错了。这种错误被称为是 “编译期” 异常。 例如:克隆异常 ? 还有一种情况:程序运行起来,编译器才会报错,这种错误被称为运行时异常, 例如越界,空指针等异常 ? ? 总结: ???所谓异常指的就是程序在 运行时 出现错误时通知调用者的一种机制 ??? 关键字 “运行时”: ???有些错误是这样的,例如将 System.out.println 拼写错了,写成了 system.out.println. 此时编译过程中就会出 错,这是 “编译期” 出错。 ? 而运行时指的是程序已经编译通过得到 class 文件了,再由 JVM 执行过程中出现的错误。 ? 异常的种类有很多,不同种类的异常具有不同的含义,也有不同的处理方式。
?
防御式编程
错误在代码中是客观存在的,因此我们要让程序出现问题的时候,及时通知程序猿。我们有两种主要的方式。 ? 1. LBYL :Look Before‘ You Leap. 在操作之前就做充分的检查。 ? 2. EAFP::It’s Easier to Ask Forgiveness than Permission;. 事后获取原谅比事前获取许可更容易。也就是先操作,遇到问题再处理。   异常的核心思想就是 EAFP.
?
LBYL风格的代码(不使用异常)
英雄联盟手游
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return;
}
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return;
}
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return;
}
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return;
}
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return;
}
......
?
EAFP 风格的代码(使用异常)
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}
......
总结
对比两种不同风格的代码, 我们可以发现, 使用第一种方式, 正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱. 而第二种方式正常流程和错误流程是分离开的, 更容易理解代码
?
异常的基本用法
捕获异常
基本语法:
>try{
有可能出现异常的语句 ;
}catch (异常类型 异常对象) {
}finally {
异常的出口
}
注意事项:
1. try 代码块中放的是可能出现异常的代码. 2. catch 代码块中放的是出现异常后的处理行为. 3. finally 代码块中的代码用于处理善后工作, 会在最后执行. 4. 其中 finally 都可以根据情况选择加或者不加.,catch必须加,不然程序会报错。
?
实例1
?
实例1的深度解析
在没处理异常的情况下,程序方式异常为什么会终止运行。
如果在不处理异常的情况下,那么这个异常会交由JVM处理,一旦交给JVM处理,程序立马从异常代码的位置终止了。 而 try,catch 是我们 人为去处理异常的方法,并不会导致程序,立马被终止,而是跳过去,继续运行,直至程序正常结束。 当然。如果 catch 能捕捉异常类型,与 try中代码发生的异常类型不符,就会导致捕捉异常失败,从而交由JVM处理,那么程序会直接终止运行。
?
错误信息与处理
?
还有一个点:判断程序是否是正常结束运行
?
重点:
异常的种类有很多, 我们要根据不同的业务场景来决定. ? 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果 (比如:程序出现异常,把我们的钱给别人红包或者转账了,那还了得,这种情况下,程序崩溃就是最好处理方式,以免造成更大损失,本来就没几个钱。。。) ? 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿 大概怎么通知,就是通过一个接口,实现一个发邮箱,或者打电话来处理。这个是真的可以做到的。后面我会写一个用代码去实现发邮箱的博客 ? 对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.(例如:网络断开,正在刷新重连) ? 在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快 速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息
?
实例2(处理空指针异常与数组越界异常 - catch 捕捉异常不限于1种)
?
异常的体系结构
Java 内置了丰富的异常体系, 用来表示不同情况下的异常. 下图表示 Java 内置的异常类之间的继承关系 顶层类 Throwable 派生出两个重要的子类, Error 和 Exception ? 其中 Error 指的是 Java 运行时内部错误和资源耗尽错误. 应用程序不抛出此类异常. 这种内部错误一旦出现, 除了告知用户并使程序终止之外, 再无能为力. 这种情况很少出现. ? Exception 是我们程序猿所使用的异常类的父类. ? 其中 Exception 有一个子类称为 RuntimeException , 这里面又派生出很多我们常见的异常类 NullPointerException , IndexOutOfBoundsException 等. ? Java语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为 非受查异常, 所有的其他异常称为 受查 异常.
?
小技巧(快速得知目前异常的父类,父类的父类。。。。)
?
前面我们catch的异常 都是 精确到某种异常类型的,而且 还想要捕获其它异常,就需要再写一个catch捕获,或者用按位或,再链接一个异常,但是现在我们知道这些异常的最终父类,那么我们可不可直接捕获它,这样我们只用写一个捕获,捕获一个异常。该异常包括所有子类异常。一劳永逸,岂不美哉。
?
还有一种捕获写法,是可以怎么去写的。来看下面程序
但是两个捕获换个位置就不行。(捕获子类异常,必须要在捕获父类异常的前面) 所以还是强调一个点,catch在捕获异常时,最好捕获具体的异常类。
?
实例3 (解决克隆异常)
?
finally 表示最后的善后工作,例如释放资源
实例4 (处理输入数据不匹配异常)
?
实例4 (技巧偷油)
?
疑问:将异常交由JVM处理,我们的finally还会被执行吗?
答案是会!
?
实例5 (处理调用方法所带来的异常)
作者心生一个疑问,如果是别人写好的一个方法,我直接拿来用。我怎么知道它当中会不会有异常,而且也不知道该异常的类型。那么该怎么做呢?来看!
?
画面一转,继续讲finally 的一些特点,来看下面
异常处理流程
1.程序先执行 try 中的代码 ? 2.如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配. ? 3.如果找到匹配的异常类型, 就会执行 catch 中的代码 ? 4.如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者. ? 5.无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行). ? 6.如果上层调用者也没有处理的了(liao)异常, 那么就继续向上传递. ? 7.一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止
?
抛出异常
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作.。 再来看 一个 throw 案例(上图中throw抛出的自定义异常,只是自定义了异常错误信息,并不是真正意义上的自定义异常类型,文章最后会讲)
?
例题(读取文件)
?
自定义异常类
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常
?
用户登录代码实例
public class Test {
private static final String userName = "author";
private static final String password = "12138";
public static void login(String userName, String password) {
if(!Test.userName.equals(userName)){
System.out.println("用户名输入错误!");
}
if (!Test.password.equals(password)){
System.out.println("password输入错误!");
}
}
public static void main(String[] args) {
login("author","12138");
}
}
?
我们的想法是将自定义一个用户登录错误异常
?
回到我们的用户登录代码
?
注意事项
自定义异常通常会继承自 Exception 或者 RuntimeException 继承自 Exception 的异常默认是受查异常 继承自 RuntimeException 的异常默认是非受查异常.
?
本文结束
|