Java 中的异常 - 非正经程序员
前段时间集合的整理真的是给我搞得心力交瘁啊,现在可以整理一些稍微简单一点的,搭配学习 ~
突然想到一个问题,这些东西我之前就整理过,现在再次整理有什么区别嘛?我就自问自答一下,可能我再次整理会看到不一样的一面,会从源码和整体来看。其次,之前的整理都是在某个地方止步,可持续性较差,现在就尽力坚持住。因为还有你们再看 ~ 另外,欢迎各行各业的同学们给我投稿,让大家都能互相了解,岂不是很棒!
进入正题,看看 Java 中的异常处理。当程序执行的时候没有按照我们的意思执行,发生了一些不可言语的 bug 时,你打算怎么搞?不管不问,还是主动出击,而 Java 语言为此就准备了一套处理异常的类。
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
两个子类,Error 和 Exception,通常用于指示发生了异常情况。通常,这些实例是在异常情况的上下文中最新创建的,因此包含了相关的信息(比如堆栈跟踪数据)。Throwable 包含了其线程创建时线程执行堆栈的快照。还可以给出错误提示信息。
Error 及其子类表示的是错误,是 JVM 本身出错了,不能由程序控制,我们最主要就是看异常。
Exception 及其子类表示的是异常,异常又分为检查异常和非检查异常,这是在 javac 编译器的角度进行分类的,检查异常在我们编写代码的时候会提示出来,我们必须要对其进行处理,处理方式有两种,下面看。非检查时异常对应于上图中的 RuntimeException ,编译的时候不报错,在运行的时候才会出现。
在 Java 中处理异常有两种思路,自己解决掉,干净利索或是抛给方法的调用者,就像你有事会找警察一样,当然警察不一定能给你解决,就像是你抛出的异常可能会被再次抛出去一样。最终会抛给 JVM ,然后程序挂掉。
自己处理主要是使用 try { } catch ( ) { } finally { } 语句块,使用 try 包裹可能出现异常的代码,使用 catch 来捕获可能出现的异常。最后 finally 语句块是用来执行那些必须要执行的代码,比方说释放资源文件,关闭数据库连接等操作。
有几点需要说明一下:
1 可以有多个 catch 语句块,但是捕获异常的顺序一定要注意,当异常出现时,只要被 catch 捕获,则下面的 catch 不再执行。所以我们要把范围小的异常种类或者说子类放在上面。
2 我们都听过 finally 语句块是一定会执行的,其实不是这样的,当遇到 system.exit() 就不会再执行了,因为你强制退出 JVM 了。除此之外,确实是一定执行的。
3 只有 catch 块才能处理异常,try 只是圈起范围的作用,而 finally 块是善后作用。
4 try catch finally 语句块中的变量都是局部变量,它们之间是不能共享的,待会看个例子。
另一种方式就是自己搞不定就使用 throws 抛出去,抛给方法的调用者,让它们去处理。在方法的声明中添加语句 throws ,即可将检查时异常抛出,后续的处理就留给方法的 caller 吧!
上面都是在检查异常发生时我们可以做的相应处理,若是运行时发生了异常,还有一种操作可以在代码执行的过程中手动的抛出一个异常,使用 throw 关键字,例如这样:
String name; if(name == "null"){ throw new IllegalArgumentException("姓名不能为空"); }
因为异常的产生是会形成连锁反应,一旦有异常发生,所有的调用者都会发生异常,异常发生最开始的地方就是异常的抛出点,抛出点的异常会向 caller 转移。所以在方法的重写中,子类抛出的异常类型一定要小于或等于父类的抛出异常类型,不然,你想想,子类 throws 出去的异常太大,父类肯定接不住呀。
最后就是要看一个例子,来看一下 return 语句在语句块中的应用。加深一下对异常处理的理解。
class Test { public int aaa() { int x = 1; try { return ++x; } catch (Exception e) { } finally { ++x; } return x; } public static void main(String[] args) { Test t = new Test(); int y = t.aaa(); System.out.println(y); } }
问:最后打印 y 是多少 ? 执行的过程是怎么的 ?
结果是 2 ,过程却可能不是那么好解释,在 try 语句中我们执行 ++x 然后就直接返回 2 ? 肯定不是,因为还要执行 finally 语句块啊,所以 Java 规范规定,在存在 finally 块时,会将 try 中的 return 放在一边,而优先执行 finally 中的代码,但是已经得到的结果 2 可没有丢了,而是存在了 try 块中的本地变量表中,以备不时之需。
在执行 finally 块之后,x 等于 3 ,一样的还是会将 3 保存在 finally 块的本地变量表中,然后再回到 try 中进行 return 操作,此时,方法结束,返回的结果就是之前保存在 try 块本地变量表中的 2 。假如我们在 finally 块中也有 return 语句,就会返回 3 而不再执行 try 中的 return 语句,然而 Java 规范不希望我们这么做,因为 finally 语句块的主要作用是为了释放资源,关闭连接操作。
结合这个例子我们要明白,在每个语句块中都会存在一个本地表量表,会保存使用到变量的值,各个语句块是分离开的,所以在执行 return 语句的时候,返回的是本地变量表中的变量。
在 finally 块中使用 return 还有一个弊端,它会抑制 try 和 catch 中的异常的抛出。看下面例子,在 try 块中也是一样的。
public static void main(String[] args) { int result; try{ result = foo(); System.out.println(result); //输出100 } catch (Exception e){ System.out.println(e.getMessage()); //没有捕获到异常 } //catch中的异常被抑制 @SuppressWarnings("finally") public static int foo() throws Exception { try { int a = 5/0; return 1; }catch(ArithmeticException amExp) { throw new Exception("我将被忽略,因为下面的finally中使用了return"); }finally { return 100; } } //try中的异常被抑制 @SuppressWarnings("finally") public static int bar() throws Exception { try { int a = 5/0; return 1; }finally { return 100; } }
总结
return 语句代表方法结束,但是遇到 finally 语句块也是要等一等的。
无论 try 里执行了 return 语句、break 语句、还是 continue 语句,finally 语句块还会继续执行,除非遇上了 system.exit() 。
finally 中不建议使用 return 语句的,释放资源就好。
推荐阅读: