华南理工大学无线电爱好者协会软件小组bugstac...

Java中的异常(下)

2017-10-27  本文已影响20人  tobe_superman

两类异常

Java中的异常继承体系为:

异常类的继承体系

这里的异常( Exception)分为两大类:Checked异常和Runtime异常。所有的RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类的实例则被称为Checked异常。
Java认为Checked异常都是可以被处理(修复)的异常,所有Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,则该程序无法通过编译(这是Checked异常与Runtime异常最大的不同)。
Checked异常体现了Java的设计哲学——没有完善处理的代码根本不会被执行!
对Checked异常的处理有两种方式:

提示:在Eclipse中,可以使用快捷键对Checked异常进行处理,即选择try...catch块来捕获异常或者throws声明抛出异常。

使用throws声明抛出异常

使用throws声明抛出异常的思路为:当前方法不知道如何处理该异常,该异常应该由上级调用者处理;如果main方法也不知道如何处理该异常,也可以使用throws声明抛出异常,将该异常交给JVM处理。JVM处理异常的方法则简单粗暴得多:打印异常的跟踪栈信息,并中止程序运行。
throws声明抛出只能在定义方法时使用,如果要抛出多个异常类,多个异常类用逗号隔开:

throws ExceptionClass1, ExceptionClass2...

下面是一个简单的例子:

public class ThrowTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

由于程序所在的目录中没有文件“a.txt”,而且main方法使用throws声明抛出异常,将异常交给JVM处理,所以程序遇到了异常后便打印异常的跟踪栈信息,并结束程序:

异常的跟踪栈信息1

如果某段代码中调用了一个带throws声明抛出的方法,该方法声明抛出了Checked异常,则表明该方法希望它的调用者来处理该异常。也就是说,调用该方法时要么放在try块中显式捕获该异常,要么放在另一个带throws声明抛出的方法中。看下面的例子:

public class ThrowTest2 {
    public static void main(String[] args) throws Exception {
        test();
    }

    public static void test() throws IOException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

对两个throws声明抛出的解释是:因为test()方法声明抛出IOException异常,所以调用该方法的代码要么处于try...catch块中,要么处于另一个带throws声明抛出的方法中;因为FileInputStream的构造器声明抛出IOException异常,所以调用该方法的代码要么处于try...catch块中,要么处于另一个带throws声明抛出的方法中。
使用throws声明抛出异常时同样有限制:

Checked异常的优劣

Checked异常有两大不便之处:

使用throw抛出异常

异常是一种很“主观”的说法,很多时候系统是否要抛出异常,可能根据应用的业务需求来决定。这时候如果因与业务需求不符而产生异常,只能有程序员自行决定抛出,系统是无法抛出的(因为并不是“客观”意义上的异常)。
throw语句抛出的不是异常实例,而是一个异常实例,而且每次只能抛出一个异常实例:

throw ExceptionInstance;

来看下面的例子:

try {
    int b = 0;
    if (b == 0) {
        throw new Exception("除数不能为零!");
    }
} catch (Exception e) {
    System.out.println("除数不能为零!");
}

当Java程序运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应的catch块,由catch块来处理该异常。
throw语句抛出的异常如果是Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,把该异常交给该方法的调用者处理;如果是Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中。程序既可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把它交给该方法的调用者处理。来看下面的例子:

public class ThrowTest {
    public static void main(String[] args) {
        try {
            //调用声明抛出Checked异常的方法
            //要么显式捕获该异常
            //要么在main方法中再次声明抛出
            throwChecked(3);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        //调用声明抛出Checked异常的方法
        //既可以显式捕获该异常
        //也可以不理会该异常
        throwRuntime(4);
    }

    public static void throwChecked(int a) throws Exception {
        if (a > 0) {
            throw new Exception("a的值大于0,不符合要求!");
        }
    }

    public static void throwRuntime(int a) {
        if (a > 0) {
            throw new RuntimeException("a的值大于0,不符合要求!");
        }
    }
}

程序抛出异常:

异常的跟踪栈信息2

上图中第一句话是Checked异常的详细描述,而下面三行红字则是Runtime异常的跟踪栈信息。
注意:throwthrows虽然只相差一个字母,用法却千差万别,使用时一定要注意区分!

自定义异常类

通常情况下,程序很少自行抛出系统异常(因为这样没有必要)。在抛出异常时,应该选择合适的异常类,从而可以明确地描述该异常情况。在这种情形下,程序需要抛出自定义异常。
自定义异常类都应该继承Exception类,如果希望自定义Runtime异常,则应该继承RuntimeException类。自定义异常类时需要提供两个构造器:一个是无参的构造器,另一个带一个字符串参数的构造器,这个字符串作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值):

public class AuctionException extends Exception{
    //无参的构造器
    public AuctionException(){
    };
    //带一个字符串参数的构造器
    public  AuctionException(String msg){
        //通过super关键字调用父类的构造器
        //将此字符串参数传给异常对象的message属性
        super(msg);
    }
}

如果要自定义Runtime异常,只需将上面代码中的父类Exception改为RuntimeException

catch和throw同时使用

在实际应用中,往往需要对异常采取复杂的处理方式——但一个异常出现时,单靠某个方法无法完全处理该异常,必须由几个方法协作才可以完全处理该异常。也就是说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常。来看下面的例子:

public class AuctionTest {
    private double initPrice = 3.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("竞拍价比起拍价低,不允许竞拍!");
        }
    }

    public static void main(String[] args) {
        AuctionTest at = new AuctionTest();
        try {
            at.bid("df");
        } catch (AuctionException ae) {
            //再次捕获到bid()方法中的异常,并对该异常进行处理
            System.err.println(ae.getMessage());
        }
    }
}

程序抛出异常:

异常的跟踪栈信息3

提示:这种catchthrow结合使用的情况在大型企业级应用中非常常用。企业级应用对异常的处理通常分成两部分:应用后台需要通过日志来记录异常发生的详细情况;应用还需要根据异常向应用使用者传达某种提示。在这种情形下,所有异常都需要两个方法共同完成,也就必须将catchthrow结合使用。

Java的异常跟踪栈

异常对象的printStackTrace()方法用于打印异常的跟踪栈信息,根据printStackTrace()方法的输出结果,开发者可以找到异常的源头,并跟踪到异常一路触发的过程。来看下面的例子:

class SelfException extends RuntimeException {
    SelfException() {
    };

    SelfException(String msg) {
        super(msg);
    }
}

public class PrintStackTraceTest {
    public static void main(String[] args) {
        firstMethod();
    }

    public static void firstMethod() {
        secondMethod();
    }

    public static void secondMethod() {
        thirdMethod();
    }

    public static void thirdMethod() {
        throw new SelfException("自定义异常信息");
    }
}

程序抛出异常:

异常的跟踪栈信息4

由上图可知,异常从thirdMethod方法开始触发,开始传到secondMethod方法,再传到firstMethod方法,最后传到main方法,在main方法终止,这个过程就是Java的异常跟踪栈。
异常的传播规律是:只要异常没有被完全捕获(包括异常额没有被捕获,或异常处理后重新抛出了新异常),异常从发生异常的方法逐渐向外传播,首先传给该方法的调用者,该方法的调用者再次传给其调用者......直到最后传给main方法,如果main方法依然没有处理该异常,JVM会中止该程序,并打印异常的跟踪栈信息。
上图的异常跟踪栈信息十分清晰地记录了程序执行停止的各个点:第一行的信息详细显示了异常的类型和详细信息;接下累跟踪栈记录程序中所有的异常发生点,各行显示被调用方法中执行的停止位置,并表明类、类中的方法名、与故障点对应的文件的行。
下面的例子展示了多线程程序中发生异常的情形:

public class ThreadExceptionTest implements Runnable {
    public void run() {
        firstMethod();
    }

    public void firstMethod() {
        secondMethod();
    }

    public void secondMethod() {
        int a = 5;
        int b = 0;
        int c = a / b;
    }

    public static void main(String[] args) {
        new Thread(new ThreadExceptionTest()).start();
    }
}

程序抛出异常:

异常的跟踪栈信息5

由上图可知,程序在Threadrun方法中出现了ArithmeticException异常,这个异常的源头是ThreadExceptionTestsecondMethod方法,这个异常传播到Thread类的run`方法就会结束。

异常处理规则

try{
    //可能引发Checked异常的代码
} catch(Throwable t) {
    //进行异常处理
    t.printStackTrace();
}
上一篇下一篇

猜你喜欢

热点阅读