第十二章 通过异常处理错误
12.1 概念
12.2 基本异常
概念:异常情形指当前方法或作用域继续执行的问题。异常情形与普通问题的区别在于,在当前情形下无法获取必要的信息来解决问题。
流程:当异常发生时,使用new在堆上创建异常对象。然后执行路径被终止并从当前环境弹出对异常对象的引用。此时异常处理机制接管程序,并且寻找一个恰当的地方继续执行程序。
if(t == null)
throw new NullPointerException();
异常使得我们可以将每件事都当做一个事件来看待,而异常可以看护者这些事务的底线。
12.2.1 异常的参数
所有标准异常类都有两个构造器:一个是默认的无参构造器,另一个接收字符串作为变量:
if(t == null)
throw new NullPointerException("t = null");
12.3.2 捕获异常
try{
//code
}catch(Type1){
//code
}catch(Type2){
//code
}
终止与恢复:错误非常关键,导致程序无法返回到异常的地方继续执行下去,一旦错误抛出,将假设错误无法挽回。另一种是恢复,修正错误,然后继续执行程序。
12.4 创建自定义的异常
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) {
//信息被发送到了System.out,需要将该对象传入其中
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)
*///:~
12.4.1 异常与记录日志
import java.util.logging.*;
import java.io.*;
class LoggingException extends Exception {
private static Logger logger =
Logger.getLogger("LoggingException");
public LoggingException() {
StringWriter trace = new StringWriter();
printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
}
public class LoggingExceptions {
public static void main(String[] args) {
try {
throw new LoggingException();
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
try {
throw new LoggingException();
} catch(LoggingException e) {
System.err.println("Caught " + e);
}
}
} /* Output: (85% match)
5月 08, 2020 2:16:42 下午 chapter12.LoggingException <init>
严重: chapter12.LoggingException
at chapter12.LoggingExceptions.main(LoggingExceptions.java:21)
Caught chapter12.LoggingException
5月 08, 2020 2:16:43 下午 chapter12.LoggingException <init>
严重: chapter12.LoggingException
at chapter12.LoggingExceptions.main(LoggingExceptions.java:26)
Caught chapter12.LoggingException
*///:~
12.5 异常说明
Java鼓励把方法可能抛出的异常说明出来,Java提供了相应的语法,并且强制使用这个语法。这样做解决了客户端程序员因为无法看到源码从而无法得知异常的位置。
void f() throws TooBig, TooSmall{//...
也可以只声明,并不真正抛出异常。这样做可以先为异常占个位置。
12.6 捕获所有异常
通过捕获异常类型的基类,就可以做到这一点。
12.6.1 栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问。该方法返回一个由栈轨迹中所有元素构成的数组,每个元素都表示栈中的一帧。元素0是栈顶元素,也是调用序列中最后一个方法的调用。
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
*///:~
12.6.2 重新抛出异常
当希望把捕获的异常再次抛出时,可以将得到的异常对象直接抛出。
catche(Exception e){
throw e;
}
如果只是将当前异常对象抛出,那么printStackTrace()方法显示的是原来的异常抛出点调用栈信息。如果更新这个信息到当前抛出点,可以调用fillInStackTrace()方法。
catche(Exception e){
throw (Exception)e.fillInStackTrace();
}
如果在捕获异常点抛出新的异常,这样的效果类似于fillInStackTrace()。
catche(Exception e){
throw new Exception();
}
12.6.3 异常链
Throwable的子类中,有三种基本的异常提供了带cause参数的构造器,分别是Error(用于Java虚拟机报告系统错误)、Exception和RuntimeException。如果把其他类型的异常链接起来,应该使用initCause而不是构造器。
DynamicFieldsException dfe =
new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;
\*Output
chapter12.DynamicFieldsException
at chapter12.DynamicFields.setField(DynamicFields.java:62)
at chapter12.DynamicFields.main(DynamicFields.java:91)
Caused by: java.lang.NullPointerException
at chapter12.DynamicFields.setField(DynamicFields.java:63)
... 1 more
*/
12.7 Java标准异常
Throwable这个java类被用来表示任何可以作为异常被抛出的类。Throwable对象可分为两种类型:Error用来表示编译时和系统错误;Exception是可以抛出的基本类型(程序员所关心的)。
12.7.1 特例:RuntimeException
属于运行时异常的类型有很多,他们会被Java虚拟机抛出,所以不必再异常说明中把他们列出来。也不需要在方法说明中抛出运行时异常,它们也被称为“不受检查的异常”。
12.8 使用finally进行清理
无论是否发生异常,finally子句中总是会被执行。
try{}
catch(Exception E){}
finally{}
12.8.1 finally用来做什么
Java会自动回收对象垃圾,但是无法清理除此之外的资源,如:已经打开的文件或网络连接,在屏幕上绘制的图形,或外部世界的开关。
12.8.2 在return中使用finally
因为finally子句总是会被执行,所以在一个方法中,可以从多个点返回,并且可以保证清理工作会进行。
public class MultipleReturns {
public static void f(int i) {
System.out.println("Initialization that requires cleanup");
try {
System.out.println("Point 1");
if(i == 1) return;
System.out.println("Point 2");
if(i == 2) return;
System.out.println("Point 3");
if(i == 3) return;
System.out.println("End");
return;
} finally {
System.out.println("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
*///:~
12.8.3 缺憾:异常的丢失
//: exceptions/LostMessage.java
// How an exception can be lost.
class VeryImportantException extends Exception {
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
public String toString() {
return "A trivial exception";
}
}
public class LostMessage {
void f() throws VeryImportantException {
throw new VeryImportantException();
}
void dispose() throws HoHumException {
throw new HoHumException();
}
public static void main(String[] args) {
try {
LostMessage lm = new LostMessage();
try {
lm.f();
} finally {
lm.dispose();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
/*
* Output: A trivial exception
*/// :~
12.9 异常的限制
当覆盖方法时,只能抛出基类方法异常说明列出的那些异常。这个限制很有用,因为这意味着,当基类使用代码应用到其他派生类对象的时候,一样能够工作。
12.10 构造器
要时刻提醒自己“如果异常发生了,所有东西能被正确的清理吗?”。如果在构造器内抛出异常,这些清理行为也许就不能正常工作了,这意味着在编写构造器时要格外小心。
下面的例子,当与具备成功执行时会执行finally子句。当执行失败时,则会跳过。
finally子句应该在构造器不抛出任何异常时也应该应用,其基本规则是:在创建需要清理的对象时,立即进入一个try-finally语句块。
public class Cleanup {
public static void main(String[] args) {
try {
InputFile in = new InputFile("Cleanup.java");
try {
String s;
int i = 1;
while((s = in.getLine()) != null)
; // Perform line-by-line processing here...
} catch(Exception e) {
System.out.println("Caught Exception in main");
e.printStackTrace(System.out);
} finally {
in.dispose();
}
} catch(Exception e) {
System.out.println("InputFile construction failed");
}
}
} /* Output:
dispose() successful
*///:~
12.11 异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的匹配程序。找到之后将进入子句,后续不再查找。
若将异常的基类放置于子类之前,编译会报错,因此无法“屏蔽”。
12.12 其他可选方式
12.13 异常使用指南
- 在恰当的级别处理问题。
- 解决问题并且重新调用产生异常的的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法会返回的值。
- 处理完当下环境能做的任务,然后把相同/不同的异常抛到更高层。
- 终止进程。
- 进行简化。(过于复杂的异常模式也会使人痛苦)
- 让类库和程序更加安全。
12.14 总结
异常处理的优点在于,可以在某处集中精力处理问题,在另一处处理当前编写代码所产生的错误。
Java坚定地将所有的错误都已异常的形式报告这一事实,使得它远超C++这类语言的长处之一。