使用try...catch的处理异常机制
Java的异常处理可以让程序具有更好的容错性,程序更加健壮。当程序运行出现意外情形时,系统会自动生成一个Exception对象来通知程序。
实现原理:如果try语句块中的业务逻辑代码在运行时出现了异常,系统自动生成一个异常对象,该异常对象被提交给Java运行时环境,这个过程被称为抛出异常。当Java运行时环境收到异常对象时,会寻找处理该异常对象的catch块,如果找到合适的catch块并把该异常对象交给catch块处理,这个过程被称为捕获异常;如果Java运行时环境找不到捕获异常的catch块,则运行时环境终止,Java程序也将退出。
try语句块后可以有多个catch块,这是为了针对不同异常类提供不同的异常处理方式。当系统发送不同异常情况时,系统会生成不同的异常对象,Java运行时环境就会根据该异常对象所属的异常类来决定使用那个一个catch语句块进行处理该异常。当Java运行时环境接受到异常对象后,会依次判断该异常对象是否是catch块后异常类或其子类的实例,如果是,Java运行时环境将调用该catch块来处理该异常;否则再次拿该异常对象和下一个catch块里的异常类进行比较。
Java提供了丰富的异常类,下面显示了Java常见的异常类直接的继承关系图:
Java把所有非正常情况分为两种:异常(Exception)和错误(Error),他们都继承Throwable父类。
Error错误:一般是指虚拟机相关的问题,如系统崩溃,虚拟机出错、动态链接失败等,这种错误无法恢复或不可能捕获,将导致引用程序中断。通常应用程序无法处理这些错误。
注意:对程序进行异常捕获时,一定是先捕获小的异常,再捕获大的异常。
访问异常信息
如果程序需要在catch代码块中访问异常对象的相关信息,可以通过调用catch语句块中的异常形参的方法来获取异常信息,当Java运行时决定调用某个catch语句块来处理该异常对象时,会将该异常对象赋给catch块后的异常参数,程序就可以通过该参数来获得异常的相关信息。
异常对象常用的几个方法:
(1)getMessage():返回该异常的详细描述字符串。
(2)printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
(3)printStackTrace(PrintStream s):将该异常信息的跟踪栈信息输出到指定输出流。
(4)getStackTrace():返回该异常的跟踪栈信息。
使用finally回收资源
Java异常处理的完整语法结构是
try {
//业务代码(可能报异常的代码)
} catch (NullPointerException e) {
// 异常处理块1
}catch(Exception e2){
//异常处理块2
}finally{
//资源回收块
}
finally语句块主要作用是回收资源,比如:关闭数据库连接,网络连接,磁盘文件,输入输出流等等。这些资源必须显示的回收,Java的垃圾回收机制不会回收任何物理资源,它只回收堆内存中对象所占用的内存。
无论try语句块中的代码是否出现异常,也不管catch块中的代码是否执行,finally中的代码块都会被执行。
经典实例:
public static void main(String[] args) {
FileInputStream fis=null;
try {
//业务代码(可能报异常的代码)
fis=new FileInputStream("a.txt");
} catch (IOException e) {
// 异常处理块1
System.out.println(e.getMessage());
//return语句强制方法返回
return;
}finally{
//资源回收块
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("程序已经执行了finally里的资源回收!");
}
}
打印结果:
a.txt (系统找不到指定的文件。)
程序已经执行了finally里的资源回收!
解析:上面程序的try块后增加了finally语句块,用于回收在try语句块中打开的物理资源。注意程序的catch块中有一条return语句,该语句强制方法返回。通常情况下,一旦在方法里执行到return语句时,程序将立即结束该方法,但这里不同,虽然return也是强制方法立即结束,但一定会在先执行finally代码块中的代码。
注意:如果在异常处理代码中使用System.exit(1)这样的语句来退出虚拟机,则finally语句块中的语句将不会被执行,其他情况都会执行finally语句块中的语句。当Java程序执行try和catch语句块遇到了return语句或者throw语句,这两个语句都会导致该方法立即结束,但系统并不会立即执行这两个语句,而是去寻找该异常处理流程中是否包含finally语句块,如果没有finally语句块,程序立即执行return或throw语句,方法终止,如果有finally语句块,系统立即开始执行finally语句块,只有当finally语句块执行完成后,系统才会再次调回执行try语句块,catch语句块里的return或throw语句。如果finally语句块中也使用了return或throw等导致方法终止的语句,则finally语句块已经终止了方法,系统将不会再跳回执行try或catch语句块中的任何代码。
异常处理嵌套:
犹如上面的代码,finally语句块中再次包含了一个完整的异处理流程,这中在try、catch、finally语句块中包含完整的异常处理流程的情况就叫异常处理嵌套。
异常处理嵌套的深度没有很明确的限制,但通常没有必要使用超过两层的嵌套异常处理,层次太深的嵌套异常处理没有太大必要,而且导致程序可读性降低。
Checked异常和Runtime异常体系
Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有RuntimeException类极其子类的实例被称为Rtuntime异常;不是RuntimeException类极其子类的异常实例则称为Checked异常。Checked异常是Java语言特有的异常。
处理Checked异常的两种方式:
(1)当前方法明确指定如果处理该异常,程序应该使用try..catch块来捕获该异常,然后在对应的catch块中修补该异常。
(2)当前方法不知道如果处理这些异常,应该在定义该方法时声明抛出该异常。
Runtime异常则更加灵活,Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch语句块来捕捉Runtime异常。
使用Throws 声明抛出异常。
当前方法不知道应该如果处理这种类型的异常,该异常应该由上一级调用者处理,如果main方法也不指定该如果处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给Java虚拟机处理。
Throws声明抛出异常只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间可以逗号隔开。
格式: public void add() throws ExceptionClass1,ExceptionClass2{ }
注意:使用Throws声明抛出异常时有一个限制:在方法重写时,子类方法中声明抛出的异常类型应该是父类方法声明抛出的异常类型的子类或相同,子类方法中不允许比父类方法声明抛出更多的异常。
Checked异常的弊端:
(1)对于程序中Checked异常,Java要求必须显示捕获并处理该异常,或者显式声明抛出。这样就增加了编程复杂度。
(2)如果在方法中显式声明抛出Checked异常,将会导致方法签名与异常耦合,如果该方法是重写父类的方法,则该方法能抛出的异常还收到被重写方法所抛出异常的限制。
Checked异常的优点:
Ckecked异常能在编译时提醒程序员代码可能存在问题,提醒程序员必须注意处理异常,或者声明该异常由该方法调用者来处理,从而避免程序员因为粗心而忘记处理该异常的错误。
使用throw抛出异常。
当程序出现错误时,系统会自动抛出异常;除此之外,Java也允许程序自行抛出异常,自行抛出异常使用throw语句完成。throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。不管是系统自动抛出的异常还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。
格式: throw ExceptionInstance;
经典实例:
public class ThrowTest {
public static void main(String[] args) {
try {
//调用带Throws声明的方法,必须显示捕获该异常,否则必须在main方法中再次声明抛出
throwChecked(-3);
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
//调用抛出Runtime异常的方法,可以显示捕获该异常,也可以不理会该异常。
throwRuntime(3);
}
public static void throwChecked(int a) throws Exception {
if (a > 0) {
//自行抛出Exception异常
throw new Exception("a的值大于另,不符合要求");
}
}
public static void throwRuntime(int a) {
if (a > 0) {
throw new RuntimeException("a的值大于0,不符合要求,runtime");
}
}
}
自定义异常类
用户自定义异常都必须继承Exception基类,如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两种构造器:一个是无参数的构造器;另一种是带一个字符串参数的构造器,这个字符串将作为该异常对象的详细说明,也就是异常对象的getMessage方法的返回值。
经典实例:
public class AuctionException extends Exception{
public AuctionException() {
super();
}
//带一个参数的构造器
public AuctionException(String msg) {
super(msg);
}
}
如果需要自定义Runtime异常类,只需将程序中的Exception基类改为RuntimeException基类,其他地方无须修改。
异常处理原则:
从程序性能和结构优化的角度给出异常处理的一般规则:
(1)使程序代码混乱最小化。
(2)捕捉并保留诊断信息。
(3)采用合适的方式结束异常活动。