1. java中所有的异常都继承自超Throwable,异常分为两大类:普通异常和错误,对应类名是Exception和Error。
普通异常继承自Exception类,他可分为检查异常和非检查异常。
检查异常:所谓检查异常就是要在编译阶段处理的异常,强制程序处理所有的checked异常,必须显示处理检查异常,如果程序没有处理,则在编译时会发生错误,无法通过编 译。 不是RuntimeException类及其子类的异常实例则被称为检查异常。
非检查异常:又叫运行时异常,在运行时候出现的异常,比如空指针、数组越界、类转换错误,被0除等,所有非检查异常继承自RuntimeException类及其子类。可以不显示的捕获和抛出,也可以显示的捕获和抛出。Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch块来捕获Runtime异常。
错误:一般是java虚拟机相关的问题,如系统崩溃,虚拟机出错误,、动态链接失败等。这种错误无法恢复和捕获,通常应用程序无法处理这些错误,因此应用程序不应该试图捕获 ERROR对象,也无须在其throws子句中声明该方法抛出任何Error或其子类,这种错误将导致程序中断。
2. 捕获和抛出异常的选择
这是对异常处理的两种方式,何时抛出?何时捕获?举一个简单的例子:除数是有可能为0的,所以进行检查时很有必要的。但除数为0代表的意义究竟是什么?通过当前正在解决问题的环境,或许能够清楚如何处理除数为0的情况。但是如果不能,就应该抛出异常。这是一个简单通用的原则,但并不是所有情况都适合。异常抛出之后会在堆中创建一个异常对象,从当前环境中弹出异常对象的引用,同时异常处理机制接管程序,并寻找一个恰当的位置来继续执行程序,这个恰当的位置便是异常处理程序。
对于可恢复的情况适用受检查异常,对于编程错误适用未受检查异常。选择决定使用哪种异常的主要原则是:如果期望调用者能够适当地恢复,对于这种情况的就应该使用受检查的异常。因为它会强迫调用者在catch子句中处理该异常或者是将其传播出去。因此,方法中声明要抛出的每个受检查的异常,都是对API用户的一种潜在提示:这个异常是有可能被挽回的。
未受检查异常往往标识编程错误,如果程序抛出未受检查异常,继续执行下去有益无害。如果程序没有捕捉到这样的可抛出结结构,将会导致当前线程停止,并出现适当的错误消息。所以一般使用运行时异常来表示编程错误。
总而言之,对于可恢复的情况适用受检查异常;对于程序错误,则适用运行时异常。当然,黑白并不是那么分明,具体的编程应该具体分析,比如造成运行时异常有可能是由于编程错误引起的,是临时的错误,那么API设计者就需要考虑资源的枯竭是否允许恢复。
public class DivideByZero { public static void main(String[] args) { try { int c = testByZero(); System.out.println("c="+c); } catch (Exception e) { System.out.println("run main exception"); //e.printStackTrace(); } } public static int testByZero() { int i = 10; int j = 0; int b = 0; try { b = i / j; } catch (ArithmeticException e /*Exception e*/) { throw e; } return b; } }
自定义异常类
继承了Throwable的类才可以为catch子句的参数类型。
用户自定义异常都应该继承Exception基类或Throwable,如果希望自定义Runtime异常,则应该继承RuntimeException基类。
自定义异常类通常需要提供两种构造器:一个是无参数的构造器,另一个是带一个字符串的构造器,这个字符串将作为该异常对象的详细说明(也就是异常对象的getMessage方法的返回值),调用super将字符串参数传给异常对象的message属性,message属性就是异常对象的详细描述信息。
使用finally回收资源
有时候,程序在try块里面打开了一些物理资源(比如数据库连接,网络连接好磁盘文件等),这些物理资源都必须显式回收。
因为:java的垃圾回收机制不会回收任何的物理资源,垃圾回收机制只回收堆内存中对象所占用的内存。
注意点1:只有try块石必须的,也就是说如果没有try块,则不可能有后面的catch块和finally块;
注意点2:catch块和finally块都是可选的,但catch块和finally块至少出现其中之一,也可以同时出现;
注意点3:可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面;
注意点4:不能只有try块,既没有catch块,也没有finally块;
注意点5:多个catch块必须位于try块之后,finally块必须位于所有catch块之后。
以上两种情况显示:除非在try块或者catch块中调用了退出虚拟机的方法(即System.exit(1);),否则不管在try块、catch块中执行怎样的代码,出现怎样的情况,异常处理的finally块总是会被执行的。不过,一般情况下,不要再finally块中使用renturn或throw等导致方法终止的语句,因为一旦使用,将会导致try块、catch块中的return、throw语句失效。
public class TestException1 { public static boolean test() { try { return true; } finally { return false; } } public static void main(String[] args) { boolean a = test(); System.out.println(a); } }
运行结果:false
以上的小程序说明:在finally块中定义了一个renturn false语句,这将导致try块中的return true 失去作用!
总结一下这个小问题:
当程序执行try块,catch块时遇到return语句或者throw语句,这两个语句都会导致该方法立即结束,所以系统并不会立即执行这两个语句,而是去寻找该异常处理流程中的finally块,如果没有finally块,程序立即执行return语句或者throw语句,方法终止。如果有finally块,系统立即开始执行finally块,只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句,如果finally块里也使用了return或throw等导致方法终止的语句,则finally块已经终止了方法,不用再跳回去执行try块、catch块里的任何代码了。
综上:尽量避免在finally块里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况!
使用throws抛出异常
使用throws抛出异常的思路是:当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不知道应该如何处理这种类型的异常,也可以使用使用throws声明抛出异常,该异常将交给JVM来处理。
JVM对异常的处理方法:打印异常跟踪栈的信息,并终止程序运行,所以有很多程序遇到异常后自动结束。
使用throws声明抛出异常时有一个限制:就是方法重写时的“两小”中的一条规则:子类方法声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或或相等,子类方法中不允许比父类方法声明抛出更多异常。即如果子类抛出的异常是父类抛出的异常的父类,那么程序无法通过编译。
因为Checked异常存在一些不便之处,大部分情况,可以使用Runtime异常,如果程序需要在合适的地方捕获异常,并对异常进行处理,程序一样可以用try...catch捕获Runtime异常。
使用throw抛出异常
1.当throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把异常交给方法的调用者处理。
2.当throw语句抛出的异常是Runtime异常,则该语句无须放在try块内,也无须放在带throws声明抛出的方法中,程序既可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法的调用者处理。
public class TestException3 { public static void throwChecked(int a) throws Exception { if (a < 0) { /** * 自行抛出Exception异常 改代码必须处于try块里,或处于带throws声明的方法中 */ throw new Exception("a的值大于0,不符合要求"); } } public static void throwRuntime(int a) { if (a < 0) { /** * 自行抛出RuntimeException异常,既可以显式捕获该异常 也可以完全不用理会该异常,把该异常交给方法的调用者处理 */ throw new RuntimeException("a的值大于0,不符合要求"); } else { System.out.println("a的值为:" + a); } } public static void main(String[] args) { try { /** * 此处调用了带throws声明的方法,必须显示捕获该异常(使用try...catch) 否则,要在main方法中再次声明抛出 */ throwChecked(-3); } catch (Exception e) { System.out.println(e.getMessage()); } throwRuntime(3); } }
但是在实际应用中往往需要更复杂的处理方式,即异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,可以让该方法的调用者也能捕获到异常。
为了实现这种靠多个方法协作处理同一个异常的情形,可以通过catch块中结合throw来完成。
public class TestException4 { // 以下AuctionException这个异常是自定义的异常类 private double initPrice = 30.0; public void bid(String bidPrice) throws AuctionException { double d = 0.0; try { d = Double.parseDouble(bidPrice); } catch (Exception e) { e.printStackTrace(); throw new AuctionException("竞拍价必须是数值,不能包含其他字符!"); } if (initPrice > d) { throw new AuctionException("竞拍价比起拍价低,不允许竞拍!"); } initPrice = d; } public static void main(String[] args) { TestException4 ta = new TestException4(); try { ta.bid("df"); } catch (AuctionException ae) { // TODO: handle exception System.err.println(ae.getMessage()); } } }