搞清原理很重要,基础的东西一定要扎实,给自己的一句话。
参考https://juejin.im/post/5b6d61e55188251b38129f9a#heading-8
java异常介绍
我们看到Java异常的基类是Throwable类型,然后它有两个派生类Error和Exception类型,然后Exception类有分为受检查异常及RuntimeException(运行时异常)。下面我们就来逐一介绍。
Error
Error一般表示编译时或者系统错误,例如:虚拟机相关的错误,系统崩溃(例如:我们开发中有时会遇到的OutOfMemoryError)等。这种错误无法恢复或不可捕获,将导致应用程序中断,通常应用程序无法处理这些错误,因此也不应该试图用catch来进行捕获。
Exception
指阻止当前方法或作用域继续执行的问题,当程序运行时出现异常时,系统就会自动生成一个Exception对象来通知程序进行相应的处理。又分为
-
受检查异常
一定需要使用try-catch包起来,否则编译会失败,这是因为这些异常类型是受检查的异常类型。编译器在编译时,对于受检异常必须进行try…catch或throws处理,否则无法通过编译。常见的受检查异常包括:IO操作、ClassNotFoundException、线程操作等。 -
运行时异常(不受检查异常)
RuntimeException及其子类都统称为非受检查异常,例如:NullPointExecrption、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等。(后面详述)
抛出异常有三种形式,
- throw
一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常 - throws
当某个方法可能会抛出某种异常时用于throws 声明可能抛出的异常,然后交给调用者处理 - 还有一种系统自动抛异常。
当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常
throws与throw比较
- throws出现声明处,throw出现先函数体
- throws代表异常的一种可能性,并不一定发生,throw则一定发生了。
- 两者都是抛出,调用者处理(如果throw处于 try 块里,想一想?)。
- Java 运行时接收到抛出的异常时,会中止当前的执行流,跳到该异常对应的 catch 块来处理该异常
好的编程习惯
- 在写程序时,对可能会出现异常的部分通常要用try{…}catch{…}去捕捉它并对它进行处理;
- 用try{…}catch{…}捕捉了异常之后一定要对在catch{…}中对其进行处理,那怕是最简单的一句输出语句,或栈输入e.printStackTrace();
- 如果是捕捉IO输入输出流中的异常,一定要在try{…}catch{…}后加finally{…}把输入输出流关闭;
- 如果在函数体内用throw抛出了某种异常,最好要在函数名中加throws抛异常声明,然后交给调用它的上层函数进行处理。
异常处理
try{
///可能会抛出异常的代码
}catch(Type1 id1){
//处理Type1类型异常的代码
}catch(Type2 id2){
//处理Type2类型异常的代码
}
try块中放置可能会发生异常的代码(但是我们不知道具体会发生哪种异常)。如果异常发生了,try块抛出系统自动生成的异常对象,然后异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序,然后进行catch语句执行(不会在向下查找)。如果我们的catch语句没有匹配到,那么JVM虚拟机还是会抛出异常的。
throws
定义一个方法时候可以用throws关键字声明,表示此方法不处理异常,而交给方法调用处处理。
当前方法不知道该如何处理该异常时,使用throws对异常进行抛出给调用者处理或者交给JVM。JVM对异常的处理方式是:打印异常的跟踪栈信息并终止程序运行。
throws在使用时应处于方法签名之后使用,可以抛出多种异常并用英文字符逗号’,’隔开。格式:
public void f() throws ClassNotFoundException,IOException{}
注意
throws的这种使用方式只是Java编译期要求我们这样做的,我们完全可以只在方法声明中throws相关异常,但是在方法里面却不抛出任何异常,
这样也能通过编译,我们通过这种方式间接的绕过了Java编译期的检查。这种方式有一个好处:为异常先占一个位置,以后就可以抛出这种异常而不需要修改已有的代码。在定义抽象类和接口的时候这种设计很重要,这样派生类或者接口实现就可以抛出这些预先声明的异常。
打印异常信息
异常类的基类Exception中提供了一组方法用来获取异常的一些信息.所以如果我们获得了一个异常对象,那么我们就可以打印出一些有用的信息,最常用的就是void printStackTrace()这个方法,这个方法将返回一个由栈轨迹中的元素所构成的数组,其中每个元素都表示栈中的一帧.元素0是栈顶元素,并且是调用序列中的最后一个方法调用(这个异常被创建和抛出之处);他有几个不同的重载版本,可以将信息输出到不同的流中去.下面的代码显示了如何打印基本的异常信息:
public void f() throws IOException{
System.out.println("Throws SimpleException from f()");
throw new IOException("Crash");
}
public static void main(String[] agrs) {
try {
new B().f();
} catch (IOException e) {
System.out.println("Caught Exception");
System.out.println("getMessage(): "+e.getMessage());
System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage());
System.out.println("toString(): "+e.toString());
System.out.println("printStackTrace(): ");
e.printStackTrace(System.out);
}
}
打印输出
Throws SimpleException from f()
Caught Exception
getMessage(): Crash
getLocalizedMessage(): Crash
toString(): java.io.IOException: Crash
printStackTrace():
java.io.IOException: Crash
at com.example.myapplication.Demo1.f(Demo1.java:10)
at com.example.myapplication.Demo1.main(Demo1.java:15)
在Java中希望除内存以外的资源恢复到它们的初始状态的时候需要使用的finally语句。例如打开的文件或者网络连接,屏幕上的绘制的图像等。下面我们来看一下案例
public class FinallyException {
static int count = 0;
public static void main(String[] args) {
while (true){
try {
if (count++ == 0){
throw new ThreeException();
}
System.out.println("no Exception");
}catch (ThreeException e){
System.out.println("ThreeException");
}finally {
System.out.println("in finally cause");
if(count == 2)
break;
}
}
}
}
class ThreeException extends Exception{}
输出
ThreeException
in finally cause// count=0,抛出异常,执行终止,不再执行System.out.println("no Exception"),跳到catch语句,执行完走到finally
no Exception //
in finally cause
自定义异常
public class Demo1 {
static class FuShuExceptiong extends Exception {
private int value;
public FuShuExceptiong(String message) {
super(message);
}
public FuShuExceptiong(String message, int value) {
super(message);
}
}
public static int div(int i, int j) throws FuShuExceptiong, Exception {
if (j < 0) {
throw new FuShuExceptiong("出现负数:" + j);
}
int temp = i / j;
System.out.println("程序未被中止");
return temp;
}
public static void main(String[] args) {
try {
System.out.println("除法操作: " + div(10, -2));
} catch (FuShuExceptiong fuShuExceptiong) {
fuShuExceptiong.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行异常 RuntimeException
- 如果在函数内容抛出该异常,函数上可以不用声明,编译一样通过。
- 如果在函数上声明了该异常。调用者可以不用进行处理。编译一样通过;
- 之所以不用在函数声明,是因为不需要让调用者处理。
当该异常发生,希望程序停止。因为在运行时,出现了无法继续运算的情况,希望停止程序后,
对代码进行修正。
异常栈
Exception异常的时候会发现,发生异常的方法会在最上层,main方法会在最下层,中间还有其他的调用层次。这其实是栈的结构,先进后出的。异常信息都是从内到外的。
异常链
有两种方式处理异常,一是throws抛出交给上级处理,二是try…catch做具体处理。但是这个与上面有什么关联呢?try…catch的catch块我们可以不需要做任何处理,仅仅只用throw这个关键字将我们封装异常信息主动抛出来。然后在通过关键字throws继续抛出该方法异常。它的上层也可以做这样的处理,以此类推就会产生一条由异常构成的异常链。
通过使用异常链,我们可以提高代码的可理解性、系统的可维护性和友好性。
**关于try catch finally 中的return问题 **
- 情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。 - 情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。 - 情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
- 情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。 - 情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。 - 情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
结论:
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值,造成异常丢失。
异常使用指南
- 在恰当的级别处理异常(在知道如何处理的情况下才捕获异常)
- 努力解决问题并且重新调用产生异常的方法
- 进行少许修补,然后绕过异常的地方重新执行
- 当前运行环境下能做的事情尽量昨晚,把异常抛到更高层
- 努力让类库更安全。
public class Demo {
class LanPingExcepting extends Exception {
public LanPingExcepting(String message) {
super(message);
}
}
class MaoYanException extends Exception {
public MaoYanException(String message) {
super(message);
}
}
static class NoPlanException extends Exception {
public NoPlanException(String message) {
super(message);
}
}
class Computer {
private int state = 3;
public void run() throws LanPingExcepting, MaoYanException {
if (state == 2) {
throw new LanPingExcepting("蓝屏了");
}
if (state == 3)
throw new MaoYanException("冒烟了");
System.out.println("电脑运行了");
}
public void reset() {
System.out.println("电脑重启");
}
}
static class Teacher {
private String name;
private Computer cmpt;
public Teacher(Computer cmpt) {
this.cmpt = cmpt;
}
public void teach() throws NoPlanException {
try {
cmpt.run();
} catch (LanPingExcepting lanPingExcepting) {
lanPingExcepting.printStackTrace();
cmpt.reset();
} catch (MaoYanException e) {
e.printStackTrace();
test();
throw new NoPlanException("无法上课");
}
}
public void test() {
System.out.println("练习");
}
}
public static void main(String[] args) {
Teacher teacher = new Teacher(new Computer());
try {
teacher.teach();
} catch (NoPlanException e) {
e.printStackTrace();
System.out.println("换电脑");
}
}
}