java编程思想读书笔记 第十二章 通过异常处理错误(中)

1.捕获所有异常
可以只写一个异常处理程序捕获所有类型的异常。通过捕获异常类型的基类Exception(事实上还有其他的基类,但Exception是同编程活动相关的基类),就可以做到这一点:

catch(Exception e) {
System.out.println("Caught an exception");
}

这将捕获所有异常,所有最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。
Exception可以调用其从基类Throwable继承的方法:

String getMessage( )
String getLocalizedMessage( )

获取详细信息(抛出异常对象所带的参数),或者用本地语言表示的详细信息。

String toString( )

返回对Throwable的简单描述,要是有详细信息的话,也会把它包含在内。

void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)

打印Throwable和Throwable的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。
Throwable fillInStackTrace( )
用于在Throwable对象的内部记录栈帧的当前状态。
此外,也可以用Throwable从基类Object继承的方法,对于异常来说,getClass()是一个好用的方法,它将返回一个表示此对象类型的对象。然后可以使用getName() 查询这个Class对象包含包信息的名称,或者使用只产生类名称getSimpleName() 方法。下面的例子是如何使用Exception类型的方法:

public class ExceptionMethods {
    public static void main(String[] args) {
        try {
            throw new Exception("My Exception");
        } catch (Exception e) {
            System.out.println("Caught Exception");
            System.out.println("getMessage():" + e.getMessage());
            System.out.println("getLocalizedMessage():"
                    + e.getLocalizedMessage());
            System.out.println("toString():" + e);
            System.out.println("printStackTrace():");
            e.printStackTrace(System.out);
        }
    }
}

/* Output:
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at com.two.ExceptionMethods.main(ExceptionMethods.java:6)
可以发现每个方法都比前一个提供了更多的信息—实际上它们每一个都是前一个的超集。

1.1栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,该方法返回一个由栈轨迹元素所构成的数组,每个元素表示栈中的一帧,元素0也是栈顶元素,是最后调用的方法(Throwable被创建和抛出之处),最后一个元素是栈底,是调用序列的第一个方法调用。简单的示例如下:

public class WhoCalled {
    static void f() {
        try {
            throw new Exception();
        } catch (Exception e) {
            for (StackTraceElement ste : e.getStackTrace()) {
                System.out.println(ste.getMethodName());
                System.out.println(ste);
            }
        }
    }
    static void g() {
        f();
    }
    static void h() {
        g();
    }
    public static void main(String[] args) {
        f();
        System.out.println("--------------------------------");
        g();
        System.out.println("--------------------------------");
        h();
    }
}

这里,打印了方法名和整个StackTraceElement,StackTraceElement它包含其他附件信息。
1.2重新抛出异常
要把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候。既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:

catch(Exception e) {
System.out.println("An exception was thrown");
throw e;
}

如果只是把异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而非重新抛出点的的信息。要想更新这个信息,可以调用fillInStackTrace()方法,这样将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。像这样:

catch(Exception e) {
System.out.println("An exception was thrown");
throw (Exception)e.fillInStackTrace();
}

调用fillInStackTrace()的这一行就成为异常的新发生地了。在异常捕获之后抛出另一种异常,其效果类似于fillInStackTrace()。
1.3异常链
在捕获一个异常后抛出另一个异常,并希望把原始异常的信息保存下来,这被称为异常链。现在所有的Throwable的子类在构造器中都可以接受一个cause对象作为参数。这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的位置。在Trowable的子类中,只有三种基本的异常类提供了带cause参数的构造器。它们是Error(用于java虚拟机报告系统错误)、Exception已经RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause()方法而不是构造器。
Throwable的子类可以在构造器中接受一个case对象作为参数。这个case参数表示原始异常,这样通过把原始异常传递给新的异常。
Throwable子类,只有三种基本异常提供了带case参数的构造器,它们是Error(用于Java虚拟机报告系统错误)、Exception以及RuntimeException。

2.java标准异常
Throwable这个java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型(指从Throwable继承而得到的类型):Error用来表示编译时和系统错误,Exception是可以被抛出的基本类型,包括Java类库,用户方法以及运行时故障都可以抛出此异常。
Error一般不用自己关心,来讲Exception
2.1特例RuntimeException
比如nullPointerException,空指针异常。
运行时产生的异常,不需要在异常说明中声明方法将抛出RuntimeException类型的异常。它们被称为“不受检查的异常”。这种异常属于错误,会被自动捕获,而不用程序员自己写代码捕获。
如果RuntimeException没有被捕获而直达main(),那么在程序退出前将调用异常的printStackTrace()方法。
请务必记住:只能在代码中忽略RuntimeException类型的异常,其他类型异常的处理都是由编译器强制实施的。RuntimeException代表的是编程错误:
1)无法预料的错误。比如从你控制范围之外传递进来的null引用。
2)作为程序员,应该在代码中进行检查错误。在一个地方发生异常,常常会在另一个地方导致错误。

3.使用finally进行清理
在异常处理程序后面加上finamlly子句,可保证无论try块里的异常是否抛出,都能执行。(通常适用于内存回收之外的情况)

try {
// The guarded region: Dangerous activities
// that might throw A, B, or C
} catch(A a1) {
// Handler for situation A
} catch(B b1) {
// Handler for situation B
} catch(C c1) {
// Handler for situation C
} finally {
// Activities that happen every time
}

3.1finally用来做什么
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。这种需要清理的资源包括:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。
甚至当当前异常1还没捕获的情况下,又抛出了个异常2,那么这个异常2的finally也会先执行,再到捕获异常1的程序块中。

public class FourException extends Exception{}
public class AlwaysFinally {
    public static void main(String[] args) {
        System.out.println("Entering first try block");
        try {
            System.out.println("Entering second try block");
            try {
                throw new FourException();
            } finally {
                System.out.println("finally in 2nd try block");
            }
        } catch (FourException e) {
            System.out.println("Caught FourException in 1st try block");
        } finally {
            System.out.println("finally in 1st try block");
        }
    }
}

/* Output:
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block

当涉及break和continue时,finally子句也会得到执行。
请注意,如果把finally子句和带标签的break及continue配合使用,在java里就没必要使用goto语句了。
3.2在return中使用finally
因为在finally子句总是会执行的,所以在一个方法中,可以从多个点返回,并且可以保证重要的清理工作仍旧会执行。在finally类的内部,从何处返回无关紧要。
3.3缺憾:异常丢失
遗憾的是,java的异常实现也有瑕疵。异常作为程序输出的标志,决不应该被忽略,但它还是有可能被轻易地忽略。用某些特殊的方式使用finally子句。
在一个异常还没得到处理的情况下,应该尽量避免抛出另一个异常。
(1)使用finally可能导致一个异常还没处理,在接下来的finally字句中又抛出了一个异常,那么前一个异常就会丢失,外面的catch块捕捉到的就是finally抛出的异常而未察觉到最开始抛出的异常。
(2)一种更简单的丢失异常的方式是在finally语句中直接return,这就更别说到catch块匹配异常了。
应该避免以上两种编程错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值