12.2 基本异常
与使用java中的其他对象一样,我们总是用new在堆上创建异常对象,这也伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器,一个是某认构造器;另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。
能够抛出任意类型的Throwable对象,它是异常类型的根类。
抛出的异常必须在某处得到处理。
虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能史它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖与抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。
创建自定义异常
创建简单的自定义异常类,可以使用默认构造器和带有字符串的构造器。
//: exceptions/FullConstructors.java
class MyException extends Exception {
public MyException() {}
public MyException(String msg) { super(msg); }
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch(MyException e) {
e.printStackTrace(System.out);
}
try {
g();
} catch(MyException e) {
e.printStackTrace(System.out);
}
}
} /* Output:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at FullConstructors.main(FullConstructors.java:19)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at FullConstructors.main(FullConstructors.java:24)
*///:~
继承结构:MyException -》Exception -》Throwable
12.5 异常说明
异常说明使用了附加的关键子throws,后面接一个所有潜在异常类型的列表。如果方法里的代码产生了异常却没有处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明这个异常。自顶向下强制执行异常说明机制。
12.6 捕获所有异常
捕获异常类型的基类Exception,就能达到捕获所有异常的情况,但是最好将其放在最后。
e.getStackTrace()可以打印出方法调用栈信息。
//: exceptions/WhoCalled.java
// Programmatic access to stack trace information.
public class WhoCalled {
static void f() {
// Generate an exception to fill in the stack trace
try {
throw new Exception();
} catch (Exception e) {
for(StackTraceElement ste : e.getStackTrace())
System.out.println(ste.getMethodName());
}
}
static void g() { f(); }
static void h() { g(); }
public static void main(String[] args) {
f();
System.out.println("--------------------------------");
g();
System.out.println("--------------------------------");
h();
}
} /* Output:
f
main
--------------------------------
f
g
main
--------------------------------
f
g
h
main
*///:~
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch子句将被忽略。要想更新这个信息,可以调用fillInStackTrace方法。
//: exceptions/Rethrowing.java
// Demonstrating fillInStackTrace()
public class Rethrowing {
public static void f() throws Exception {
System.out.println("originating the exception in f()");
throw new Exception("thrown from f()");
}
public static void g() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside g(),e.printStackTrace()");
e.printStackTrace(System.out);
throw e;
}
}
public static void h() throws Exception {
try {
f();
} catch(Exception e) {
System.out.println("Inside h(),e.printStackTrace()");
e.printStackTrace(System.out);
throw (Exception)e.fillInStackTrace();
}
}
public static void main(String[] args) {
try {
g();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
try {
h();
} catch(Exception e) {
System.out.println("main: printStackTrace()");
e.printStackTrace(System.out);
}
}
} /* Output:
originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.g(Rethrowing.java:11)
at Rethrowing.main(Rethrowing.java:29)
originating the exception in f()
Inside h(),e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:7)
at Rethrowing.h(Rethrowing.java:20)
at Rethrowing.main(Rethrowing.java:35)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:24)
at Rethrowing.main(Rethrowing.java:35)
*///:~
调用fillInStackTrace就成了异常的新发生地了。
永远不必为清理一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用new在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。
12.7 java标准异常
Throwable有两种实现类型:Error用来表示编译时和系统错误;Exception可以被抛出的基本类型。
属于运行时异常的类型有很多,它们会自动被java虚拟机抛出,所以不必在异常说明中把它们列出来。如果没有对其进行捕获,那么程序会在退出前将调用异常的printStackTrace方法。
12.8 使用finally进行清理
当java中的异常不允许我们回到异常抛出的地点时,如果把try块方在循环里,就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使得循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。
异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行finally子句。
//: exceptions/AlwaysFinally.java
// Finally is always executed.
import static net.mindview.util.Print.*;
class FourException extends Exception {}
public class AlwaysFinally {
public static void main(String[] args) {
print("Entering first try block");
try {
print("Entering second try block");
try {
throw new FourException();
} finally {
print("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
*///:~
//: exceptions/MultipleReturns.java
import static net.mindview.util.Print.*;
public class MultipleReturns {
public static void f(int i) {
print("Initialization that requires cleanup");
try {
print("Point 1");
if(i == 1) return;
print("Point 2");
if(i == 2) return;
print("Point 3");
if(i == 3) return;
print("End");
return;
} finally {
print("Performing cleanup");
}
}
public static void main(String[] args) {
for(int i = 1; i <= 4; i++)
f(i);
}
} /* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
*///:~
异常丢失,在finally中的方法抛出了另一种异常,则之前的异常信息就会丢失,或者在finally中调用return。解决这个问题需要使用异常链来保存之前抛出的异常。
12.9 异常的限制
当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常。
异常限制对构造器不起作用。派生类构造器不能捕获基类构造器抛出的异常。
异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。不能基于异常说明来重载方法。
12.10 构造器
对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try语句:
在创建需要清理的对象之后,立即进入一个try-finally语句块。
12.11 异常匹配
异常处理会按照代码顺序找出最近的处理程序,匹配之后就不再继续查找。查找的时候派生类的对象也可以匹配其基类的处理程序。
12.13 异常使用指南
- 在恰当的级别处理问题(在知道该如何处理的情况下才捕获异常。)
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,意替代方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
- 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
- 终止程序。
- 进行简化(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人)
- 让类库和程序更安全。
网友评论