15-Exceptions
第十五章 异常
Java 的基本理念是“结构不佳的代码不能运行”。
改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素,但是在
发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。这就需要错误源能通过某种方式,把适当的信息传递给某个接收者——该接收者将知道如何正确处理这个问题。
要想创建健壮的系统,它的每一个构件都必须是健壮的。
因为异常处理是
异常概念
解决的办法是,用强制规定的形式来消除错误处理过程中随心所欲的因素。这种做法由来已久,对异常处理的实现可以追溯到
“异常”这个词有“我对此感到意外”的意思。问题出现了,你也许不清楚该如何处理,但你的确知道不应该置之不理,你要停下来,看看是不是有别人或在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在那里将作出正确的决定。
异常往往能降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。理想情况下,只需在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
基本异常
异常情形(exceptional condition)是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题相区分很重要,所谓的普通问题是指,在当前环境下能得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了,因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出,并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
除法就是一个简单的例子。除数有可能为
当抛出异常后,有几件事会随之发生。首先,同
举一个抛出异常的简单例子。对于对象引用
if(t == null)
throw new NullPointerException();
这就抛出了异常,于是在当前环境下就不必再为这个问题操心了,它将在别的地方得到处理。具体是哪个“地方”后面很快就会介绍。
异常允许你将做的每件事都当作一个事务来考虑,而异常守护着这些事务
异常参数
与使用
throw new NullPointerException("t = null");
不久读者将看到,要把这个字符串的内容提取出来可以有多种不同的方法。
关键字
抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同
此外,能够抛出任意类型的
异常捕获
要明白异常是如何被捕获的,必须首先理解监控区域(guarded region)的概念。它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
try 语句块
如果在方法内部抛出了异常(或者在方法内部调用的其他方法抛出了异常
try {
// Code that might generate exceptions
}
对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法调用的前后加上设置和错误检查的代码,甚至在每次调用同一方法时也得这么做。有了异常处理机制,可以把所有动作都放在
异常处理程序
当然,抛出的异常必须在某处得到处理。这个“地点”就是异常处理程序,而且针对每个要捕获的异常,得准备相应的处理程序。异常处理程序紧跟在
try {
// Code that might generate exceptions
} catch(Type1 id1) {
// Handle exceptions of Type1
} catch(Type2 id2) {
// Handle exceptions of Type2
} catch(Type3 id3) {
// Handle exceptions of Type3
}
// etc.
每个
异常处理程序必须紧跟在
注意在
终止与恢复
异常处理理论上有两种基本模型。
另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法,并认为第二次能成功。对于恢复模型,通常希望异常被处理之后能继续执行程序。如果想要用
在过去,使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码,并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人,但不是很实用。其中的主要原因可能是它所导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能会从许多地方抛出的大型程序来说,更是如此。
自定义异常
不必拘泥于
要自己定义异常类,必须从已有的异常类继承,最好是选择意思相近的异常类继承(不过这样的异常并不容易找
// exceptions/InheritingExceptions.java
// Creating your own exceptions
class SimpleException extends Exception {}
public class InheritingExceptions {
public void f() throws SimpleException {
System.out.println(
"Throw SimpleException from f()");
throw new SimpleException();
}
public static void main(String[] args) {
InheritingExceptions sed =
new InheritingExceptions();
try {
sed.f();
} catch(SimpleException e) {
System.out.println("Caught it!");
}
}
}
输出为:
Throw SimpleException from f()
Caught it!
编译器创建了无参构造器,它将自动调用基类的无参构造器。本例中不会得到像
本例的结果被显示在控制台。你也可以通过写入
你也可以为异常类创建一个接受字符串参数的构造器:
// exceptions/FullConstructors.java
class MyException extends Exception {
MyException() {}
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);
}
}
}
输出为:
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)
新增的代码非常简短:两个构造器定义了
在异常处理程序中,调用了在
e.printStackTrace();
信息就会被输出到标准错误流。
异常与记录日志
你可能还想使用
// exceptions/LoggingExceptions.java
// An exception that reports through a Logger
// {ErrorOutputExpected}
import java.util.logging.*;
import java.io.*;
class LoggingException extends Exception {
private static Logger logger =
Logger.getLogger("LoggingException");
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);
}
}
}
输出为:
___[ Error Output ]___
May 09, 2017 6:07:17 AM LoggingException <init>
SEVERE: LoggingException
at
LoggingExceptions.main(LoggingExceptions.java:20)
Caught LoggingException
May 09, 2017 6:07:17 AM LoggingException <init>
SEVERE: LoggingException
at
LoggingExceptions.main(LoggingExceptions.java:25)
Caught LoggingException
静态的
尽管由于
// exceptions/LoggingExceptions2.java
// Logging caught exceptions
// {ErrorOutputExpected}
import java.util.logging.*;
import java.io.*;
public class LoggingExceptions2 {
private static Logger logger =
Logger.getLogger("LoggingExceptions2");
static void logException(Exception e) {
StringWriter trace = new StringWriter();
e.printStackTrace(new PrintWriter(trace));
logger.severe(trace.toString());
}
public static void main(String[] args) {
try {
throw new NullPointerException();
} catch(NullPointerException e) {
logException(e);
}
}
}
输出结果为:
___[ Error Output ]___
May 09, 2017 6:07:17 AM LoggingExceptions2 logException
SEVERE: java.lang.NullPointerException
at
LoggingExceptions2.main(LoggingExceptions2.java:17)
还可以更进一步自定义异常,比如加入额外的构造器和成员:
// exceptions/ExtraFeatures.java
// Further embellishment of exception classes
class MyException2 extends Exception {
private int x;
MyException2() {}
MyException2(String msg) { super(msg); }
MyException2(String msg, int x) {
super(msg);
this.x = x;
}
public int val() { return x; }
@Override
public String getMessage() {
return "Detail Message: "+ x
+ " "+ super.getMessage();
}
}
public class ExtraFeatures {
public static void f() throws MyException2 {
System.out.println(
"Throwing MyException2 from f()");
throw new MyException2();
}
public static void g() throws MyException2 {
System.out.println(
"Throwing MyException2 from g()");
throw new MyException2("Originated in g()");
}
public static void h() throws MyException2 {
System.out.println(
"Throwing MyException2 from h()");
throw new MyException2("Originated in h()", 47);
}
public static void main(String[] args) {
try {
f();
} catch(MyException2 e) {
e.printStackTrace(System.out);
}
try {
g();
} catch(MyException2 e) {
e.printStackTrace(System.out);
}
try {
h();
} catch(MyException2 e) {
e.printStackTrace(System.out);
System.out.println("e.val() = " + e.val());
}
}
}
输出为:
Throwing MyException2 from f()
MyException2: Detail Message: 0 null
at ExtraFeatures.f(ExtraFeatures.java:24)
at ExtraFeatures.main(ExtraFeatures.java:38)
Throwing MyException2 from g()
MyException2: Detail Message: 0 Originated in g()
at ExtraFeatures.g(ExtraFeatures.java:29)
at ExtraFeatures.main(ExtraFeatures.java:43)
Throwing MyException2 from h()
MyException2: Detail Message: 47 Originated in h()
at ExtraFeatures.h(ExtraFeatures.java:34)
at ExtraFeatures.main(ExtraFeatures.java:48)
e.val() = 47
新的异常添加了字段
既然异常也是对象的一种,所以可以继续修改这个异常类,以得到更强的功能。但要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,其他的就不管了(大多数
异常声明
异常说明使用了附加的关键字
void f() throws TooBig, TooSmall, DivZero { // ...
但是,要是这样写:
void f() { // ...
就表示此方法不会抛出任何异常(除了从
代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理这个异常,要么就在异常说明中表明此方法将产生异常。通过这种自顶向下强制执行的异常说明机制,
不过还是有个能“作弊”的地方:可以声明方法将抛出异常,实际上却不抛出。编译器相信了这个声明,并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。
这种在编译时被强制检查的异常称为被检查的异常。
捕获所有异常
可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类
catch(Exception e) {
System.out.println("Caught an exception");
}
这将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他处理程序之前先把异常捕获了。
因为
String getMessage()
String getLocalizedMessage()
用来获取详细信息,或用本地语言表示的详细信息。
String toString()
返回对
void printStackTrace()
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
打印
Throwable fillInStackTrace()
用于在
此外,也可以使用
下面的例子演示了如何使用
// exceptions/ExceptionMethods.java
// Demonstrating the Exception Methods
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);
}
}
}
输出为:
Caught Exception
getMessage():My Exception
getLocalizedMessage():My Exception
toString():java.lang.Exception: My Exception
printStackTrace():
java.lang.Exception: My Exception
at ExceptionMethods.main(ExceptionMethods.java:7)
可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。
多重捕获
如果有一组具有相同基类的异常,你想使用同一方式进行捕获,那你直接
// exceptions/SameHandler.java
class EBase1 extends Exception {}
class Except1 extends EBase1 {}
class EBase2 extends Exception {}
class Except2 extends EBase2 {}
class EBase3 extends Exception {}
class Except3 extends EBase3 {}
class EBase4 extends Exception {}
class Except4 extends EBase4 {}
public class SameHandler {
void x() throws Except1, Except2, Except3, Except4 {}
void process() {}
void f() {
try {
x();
} catch(Except1 e) {
process();
} catch(Except2 e) {
process();
} catch(Except3 e) {
process();
} catch(Except4 e) {
process();
}
}
}
通过
// exceptions/MultiCatch.java
public class MultiCatch {
void x() throws Except1, Except2, Except3, Except4 {}
void process() {}
void f() {
try {
x();
} catch(Except1 | Except2 | Except3 | Except4 e) {
process();
}
}
}
或者以其他的组合方式:
// exceptions/MultiCatch2.java
public class MultiCatch2 {
void x() throws Except1, Except2, Except3, Except4 {}
void process1() {}
void process2() {}
void f() {
try {
x();
} catch(Except1 | Except2 e) {
process1();
} catch(Except3 | Except4 e) {
process2();
}
}
}
这对书写更整洁的代码很有帮助。
栈轨迹
// 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();
}
}
输出为:
f
main
*******
f
g
main
*******
f
g
h
main
这里,我们只打印了方法名,但实际上还可以打印整个
重新抛出异常
有时希望把刚捕获的异常重新抛出,尤其是在使用
catch(Exception e) {
System.out.println("An exception was thrown");
throw e;
}
重抛异常会把异常抛给上一级环境中的异常处理程序,同一个
如果只是把当前异常对象重新抛出,那么
// 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);
}
}
}
输出为:
originating the exception in f()
Inside g(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:8)
at Rethrowing.g(Rethrowing.java:12)
at Rethrowing.main(Rethrowing.java:32)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:8)
at Rethrowing.g(Rethrowing.java:12)
at Rethrowing.main(Rethrowing.java:32)
originating the exception in f()
Inside h(), e.printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.f(Rethrowing.java:8)
at Rethrowing.h(Rethrowing.java:22)
at Rethrowing.main(Rethrowing.java:38)
main: printStackTrace()
java.lang.Exception: thrown from f()
at Rethrowing.h(Rethrowing.java:27)
at Rethrowing.main(Rethrowing.java:38)
调用
有可能在捕获异常之后抛出另一种异常。这么做的话,得到的效果类似于使用
// exceptions/RethrowNew.java
// Rethrow a different object from the one you caught
class OneException extends Exception {
OneException(String s) { super(s); }
}
class TwoException extends Exception {
TwoException(String s) { super(s); }
}
public class RethrowNew {
public static void f() throws OneException {
System.out.println(
"originating the exception in f()");
throw new OneException("thrown from f()");
}
public static void main(String[] args) {
try {
try {
f();
} catch(OneException e) {
System.out.println(
"Caught in inner try, e.printStackTrace()");
e.printStackTrace(System.out);
throw new TwoException("from inner try");
}
} catch(TwoException e) {
System.out.println(
"Caught in outer try, e.printStackTrace()");
e.printStackTrace(System.out);
}
}
}
输出为:
originating the exception in f()
Caught in inner try, e.printStackTrace()
OneException: thrown from f()
at RethrowNew.f(RethrowNew.java:16)
at RethrowNew.main(RethrowNew.java:21)
Caught in outer try, e.printStackTrace()
TwoException: from inner try
at RethrowNew.main(RethrowNew.java:26)
最后那个异常仅知道自己来自
永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用
精准的重新抛出异常
在
class BaseException extends Exception {}
class DerivedException extends BaseException {}
public class PreciseRethrow {
void catcher() throws DerivedException {
try {
throw new DerivedException();
} catch(BaseException e) {
throw e;
}
}
}
因为
异常链
常常会想要在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这被称为异常链。在
有趣的是,在
下面的例子能让你在运行时动态地向
// exceptions/DynamicFields.java
// A Class that dynamically adds fields to itself to
// demonstrate exception chaining
class DynamicFieldsException extends Exception {}
public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for(int i = 0; i < initialSize; i++)
fields[i] = new Object[] { null, null };
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for(Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}
private int hasField(String id) {
for(int i = 0; i < fields.length; i++)
if(id.equals(fields[i][0]))
return i;
return -1;
}
private int getFieldNumber(String id)
throws NoSuchFieldException {
int fieldNum = hasField(id);
if(fieldNum == -1)
throw new NoSuchFieldException();
return fieldNum;
}
private int makeField(String id) {
for(int i = 0; i < fields.length; i++)
if(fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// No empty fields. Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for(int i = 0; i < fields.length; i++)
tmp[i] = fields[i];
for(int i = fields.length; i < tmp.length; i++)
tmp[i] = new Object[] { null, null };
fields = tmp;
// Recursive call with expanded fields:
return makeField(id);
}
public Object
getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
public Object setField(String id, Object value)
throws DynamicFieldsException {
if(value == null) {
// Most exceptions don't have a "cause"
// constructor. In these cases you must use
// initCause(), available in all
// Throwable subclasses.
DynamicFieldsException dfe =
new DynamicFieldsException();
dfe.initCause(new NullPointerException());
throw dfe;
}
int fieldNumber = hasField(id);
if(fieldNumber == -1)
fieldNumber = makeField(id);
Object result = null;
try {
result = getField(id); // Get old value
} catch(NoSuchFieldException e) {
// Use constructor that takes "cause":
throw new RuntimeException(e);
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println("df: " + df);
System.out.println("df.getField(\"d\") : "
+ df.getField("d"));
Object field =
df.setField("d", null); // Exception
} catch(NoSuchFieldException |
DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
输出为:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField("d") : A new value for d
DynamicFieldsException
at DynamicFields.setField(DynamicFields.java:65)
at DynamicFields.
Caused by: java.lang.NullPointerException
at DynamicFields.setField(DynamicFields.java:67)
... 1 more
每个
至于返回值,
你会注意到,
main()
方法中的
Java 标准异常
基本理念是用异常的名称代表发生的问题。异常的名称应该可以望文知意。异常并非全是在
特例:RuntimeException
在本章的第一个例子中:
if(t == null)
throw new NullPointerException();
如果必须对传递给方法的每个引用都检查其是否为
属于运行时异常的类型有很多,它们被RuntimeException
的子类而把它们归类起来,这是继承的一个绝佳例子:建立具有相同特征和行为的一组类型。
- 无法预料的错误。比如从你控制范围之外传递进来的
null 引用。 - 作为程序员,应该在代码中进行检查的错误
。 (比如对于ArrayIndexOutOfBoundsException ,就得注意一下数组的大小了。 )在一个地方发生的异常,常常会在另一个地方导致错误。
在这些情况下使用异常很有好处,它们能给调试带来便利。
如果不捕获这种类型的异常会发生什么事呢?因为编译器没有在这个问题上对异常说明进行强制检查,
// exceptions/NeverCaught.java
// Ignoring RuntimeExceptions
// {ThrowsException}
public class NeverCaught {
static void f() {
throw new RuntimeException("From f()");
}
static void g() {
f();
}
public static void main(String[] args) {
g();
}
}
输出结果为:
___[ Error Output ]___
Exception in thread "main" java.lang.RuntimeException:
From f()
at NeverCaught.f(NeverCaught.java:7)
at NeverCaught.g(NeverCaught.java:10)
at NeverCaught.main(NeverCaught.java:13)
如果
你会发现,RuntimeException(或任何从它继承的异常)是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告给了
请务必记住:代码中只有
值得注意的是:不应把
使用finally 进行清理
有一些代码片段,可能会希望无论
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
}
为了证明
// exceptions/FinallyWorks.java
// The finally clause is always executed
class ThreeException extends Exception {}
public class FinallyWorks {
static int count = 0;
public static void main(String[] args) {
while(true) {
try {
// Post-increment is zero first time:
if(count++ == 0)
throw new ThreeException();
System.out.println("No exception");
} catch(ThreeException e) {
System.out.println("ThreeException");
} finally {
System.out.println("In finally clause");
if(count == 2) break; // out of "while"
}
}
}
}
输出为:
ThreeException
In finally clause
No exception
In finally clause
从输出中发现,无论异常是否被抛出,
finally 用来做什么?
对于没有垃圾回收和析构函数自动调用机制的语言来说,
当要把除内存之外的资源恢复到它们的初始状态时,就要用到
// exceptions/Switch.java
public class Switch {
private boolean state = false;
public boolean read() { return state; }
public void on() {
state = true;
System.out.println(this);
}
public void off() {
state = false;
System.out.println(this);
}
@Override
public String toString() {
return state ? "on" : "off";
}
}
// exceptions/OnOffException1.java
public class OnOffException1 extends Exception {}
// exceptions/OnOffException2.java
public class OnOffException2 extends Exception {}
// exceptions/OnOffSwitch.java
// Why use finally?
public class OnOffSwitch {
private static Switch sw = new Switch();
public static void f()
throws OnOffException1, OnOffException2 {}
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
f();
sw.off();
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
sw.off();
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
sw.off();
}
}
}
输出为:
on
off
程序的目的是要确保
// exceptions/WithFinally.java
// Finally Guarantees cleanup
public class WithFinally {
static Switch sw = new Switch();
public static void main(String[] args) {
try {
sw.on();
// Code that can throw exceptions...
OnOffSwitch.f();
} catch(OnOffException1 e) {
System.out.println("OnOffException1");
} catch(OnOffException2 e) {
System.out.println("OnOffException2");
} finally {
sw.off();
}
}
}
输出为:
on
off
这里
甚至在异常没有被当前的异常处理程序捕获的情况下,异常处理机制也会在跳到更高一层的异常处理程序之前,执行
// exceptions/AlwaysFinally.java
// Finally is always executed
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");
}
}
}
输出为:
Entering first try block
Entering second try block
finally in 2nd try block
Caught FourException in 1st try block
finally in 1st try block
当涉及
在return 中使用finally
因为
// exceptions/MultipleReturns.java
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);
}
}
输出为:
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
从输出中可以看出,从何处返回无关紧要,
缺憾:异常丢失
遗憾的是,
// exceptions/LostMessage.java
// How an exception can be lost
class VeryImportantException extends Exception {
@Override
public String toString() {
return "A very important exception!";
}
}
class HoHumException extends Exception {
@Override
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(VeryImportantException | HoHumException e) {
System.out.println(e);
}
}
}
输出为:
A trivial exception
从输出中可以看到,
一种更加简单的丢失异常的方式是从
// exceptions/ExceptionSilencer.java
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
// Using 'return' inside the finally block
// will silence any thrown exception.
return;
}
}
}
如果运行这个程序,就会看到即使方法里抛出了异常,它也不会产生任何输出。
异常限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为这意味着与基类一起工作的代码,也能和导出类一起正常工作(这是面向对象的基本概念
下面例子演示了这种(在编译时)施加在异常上面的限制:
// exceptions/StormyInning.java
// Overridden methods can throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions
class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}
abstract class Inning {
Inning() throws BaseballException {}
public void event() throws BaseballException {
// Doesn't actually have to throw anything
}
public abstract void atBat() throws Strike, Foul;
public void walk() {} // Throws no checked exceptions
}
class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}
interface Storm {
void event() throws RainedOut;
void rainHard() throws RainedOut;
}
public class StormyInning extends Inning implements Storm {
// OK to add new exceptions for constructors, but you
// must deal with the base constructor exceptions:
public StormyInning()
throws RainedOut, BaseballException {}
public StormyInning(String s)
throws BaseballException {}
// Regular methods must conform to base class:
//- void walk() throws PopFoul {} //Compile error
// Interface CANNOT add exceptions to existing
// methods from the base class:
//- public void event() throws RainedOut {}
// If the method doesn't already exist in the
// base class, the exception is OK:
@Override
public void rainHard() throws RainedOut {}
// You can choose to not throw any exceptions,
// even if the base version does:
@Override
public void event() {}
// Overridden methods can throw inherited exceptions:
@Override
public void atBat() throws PopFoul {}
public static void main(String[] args) {
try {
StormyInning si = new StormyInning();
si.atBat();
} catch(PopFoul e) {
System.out.println("Pop foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
// Strike not thrown in derived version.
try {
// What happens if you upcast?
Inning i = new StormyInning();
i.atBat();
// You must catch the exceptions from the
// base-class version of the method:
} catch(Strike e) {
System.out.println("Strike");
} catch(Foul e) {
System.out.println("Foul");
} catch(RainedOut e) {
System.out.println("Rained out");
} catch(BaseballException e) {
System.out.println("Generic baseball exception");
}
}
}
在
接口
异常限制对构造器不起作用。你会发现
派生类构造器不能捕获基类构造器抛出的异常。
Inning
派生类的对象时,就会抛出异常,从而导致程序出现问题。通过强制派生类遵守基类方法的异常说明,对象的可替换性得到了保证。
覆盖后的atBat()
上,它抛出的异常PopFoul
是由基类版atBat()
抛出的Foul
异常派生而来。如果你写的代码同 Inning
一起工作,并且调用了 atBat()
的话,那么肯定能捕获 Foul
。又因为 PopFoul
是由 Foul
派生而来,因此异常处理程序也能捕获 PopFoul
。
最后一个有趣的地方在 main()
。如果处理的刚好是
尽管在继承过程中,编译器会对异常说明做强制要求,但异常说明本身并不属于方法类型的一部分,方法类型是由方法的名字与参数的类型组成的。因此,不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同,在继承中,基类的方法必须出现在派生类里,换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。
构造器
有一点很重要,即你要时刻询问自己“如果异常发生了,所有东西能被正确的清理吗
你也许会认为使用
在下面的例子中,建立了一个
// exceptions/InputFile.java
// Paying attention to exceptions in constructors
import java.io.*;
public class InputFile {
private BufferedReader in;
public InputFile(String fname) throws Exception {
try {
in = new BufferedReader(new FileReader(fname));
// Other code that might throw exceptions
} catch(FileNotFoundException e) {
System.out.println("Could not open " + fname);
// Wasn't open, so don't close it
throw e;
} catch(Exception e) {
// All other exceptions must close it
try {
in.close();
} catch(IOException e2) {
System.out.println("in.close() unsuccessful");
}
throw e; // Rethrow
} finally {
// Don't close it here!!!
}
}
public String getLine() {
String s;
try {
s = in.readLine();
} catch(IOException e) {
throw new RuntimeException("readLine() failed");
}
return s;
}
public void dispose() {
try {
in.close();
System.out.println("dispose() successful");
} catch(IOException e2) {
throw new RuntimeException("in.close() failed");
}
}
}
如果
在本例中,由于
用户在不再需要
对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的
// exceptions/Cleanup.java
// Guaranteeing proper cleanup of a resource
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");
}
}
}
输出为:
dispose() successful
请仔细观察这里的逻辑:对
这种通用的清理惯用法在构造器不抛出任何异常时也应该运用,其基本规则是:在创建需要清理的对象之后,立即进入一个
// exceptions/CleanupIdiom.java
// Disposable objects must be followed by a try-finally
class NeedsCleanup { // Construction can't fail
private static long counter = 1;
private final long id = counter++;
public void dispose() {
System.out.println(
"NeedsCleanup " + id + " disposed");
}
}
class ConstructionException extends Exception {}
class NeedsCleanup2 extends NeedsCleanup {
// Construction can fail:
NeedsCleanup2() throws ConstructionException {}
}
public class CleanupIdiom {
public static void main(String[] args) {
// [1]:
NeedsCleanup nc1 = new NeedsCleanup();
try {
// ...
} finally {
nc1.dispose();
}
// [2]:
// If construction cannot fail,
// you can group objects:
NeedsCleanup nc2 = new NeedsCleanup();
NeedsCleanup nc3 = new NeedsCleanup();
try {
// ...
} finally {
nc3.dispose(); // Reverse order of construction
nc2.dispose();
}
// [3]:
// If construction can fail you must guard each one:
try {
NeedsCleanup2 nc4 = new NeedsCleanup2();
try {
NeedsCleanup2 nc5 = new NeedsCleanup2();
try {
// ...
} finally {
nc5.dispose();
}
} catch(ConstructionException e) { // nc5 const.
System.out.println(e);
} finally {
nc4.dispose();
}
} catch(ConstructionException e) { // nc4 const.
System.out.println(e);
}
}
}
输出为:
NeedsCleanup 1 disposed
NeedsCleanup 3 disposed
NeedsCleanup 2 disposed
NeedsCleanup 5 disposed
NeedsCleanup 4 disposed
[1] 相当简单,遵循了在可去除对象之后紧跟try-finally 的原则。如果对象构造不会失败,就不需要任何catch 。[2] 为了构造和清理,可以看到将具有不能失败的构造器的对象分组在一起。[3] 展示了如何处理那些具有可以失败的构造器,且需要清理的对象。为了正确处理这种情况,事情变得很棘手,因为对于每一个构造,都必须包含在其自己的try-finally 语句块中,并且每一个对象构造必须都跟随一个try-finally 语句块以确保清理。
本例中异常处理的混乱情形,有力的论证了应该创建不会抛出异常的构造器,尽管这并不总会实现。
注意,如果
Try-With-Resources 用法
上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时,找出放置所有
InputFile.java
是一个特别棘手的情况,因为文件被打开(伴随所有可能因此产生的异常getLine()
都可能导致异常,而且 dispose()
也是这种情况。这个例子只是好在它显示了事情可以混乱到什么地步。它还表明了你应该尽量不要那样设计代码(当然,你经常会遇到这种无法选择的代码设计的情况,因此你仍然必须要理解它
// exceptions/InputFile2.java
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
public class InputFile2 {
private String fname;
public InputFile2(String fname) {
this.fname = fname;
}
public Stream<String> getLines() throws IOException {
return Files.lines(Paths.get(fname));
}
public static void
main(String[] args) throws IOException {
new InputFile2("InputFile2.java").getLines()
.skip(15)
.limit(1)
.forEach(System.out::println);
}
}
输出为:
main(String[] args) throws IOException {
现在,
你不能总是轻易地回避这个问题。有时会有以下问题:
- 需要资源清理
- 需要在特定的时刻进行资源清理,比如你离开作用域的时候(在通常情况下意味着通过异常进行清理
) 。
一个常见的例子是 java.io.FileInputStream
(将会在 附录:
// exceptions/MessyExceptions.java
import java.io.*;
public class MessyExceptions {
public static void main(String[] args) {
InputStream in = null;
try {
in = new FileInputStream(
new File("MessyExceptions.java"));
int contents = in.read();
// Process contents
} catch(IOException e) {
// Handle the error
} finally {
if(in != null) {
try {
in.close();
} catch(IOException e) {
// Handle the close() error
}
}
}
}
}
当
幸运的是,
// exceptions/TryWithResources.java
import java.io.*;
public class TryWithResources {
public static void main(String[] args) {
try(
InputStream in = new FileInputStream(
new File("TryWithResources.java"))
) {
int contents = in.read();
// Process contents
} catch(IOException e) {
// Handle the error
}
}
}
在in
在整个
它是如何工作的? java.lang.AutoCloseable
接口,这个接口只有一个方法:close()
。当在AutoCloseable
时,许多接口和类被修改以实现它;查看Stream
对象:
// exceptions/StreamsAreAutoCloseable.java
import java.io.*;
import java.nio.file.*;
import java.util.stream.*;
public class StreamsAreAutoCloseable {
public static void
main(String[] args) throws IOException{
try(
Stream<String> in = Files.lines(
Paths.get("StreamsAreAutoCloseable.java"));
PrintWriter outfile = new PrintWriter(
"Results.txt"); // [1]
) {
in.skip(5)
.limit(1)
.map(String::toLowerCase)
.forEachOrdered(outfile::println);
} // [2]
}
}
[1] 你在这里可以看到其他的特性:资源规范头中可以包含多个定义,并且通过分号进行分割(最后一个分号是可选的) 。规范头中定义的每个对象都会在try 语句块运行结束之后调用close() 方法。[2] try-with-resources 里面的try 语句块可以不包含catch 或者finally 语句而独立存在。在这里,IOException 被main() 方法抛出,所以这里并不需要在try 后面跟着一个catch 语句块。
揭示细节
为了研究
// exceptions/AutoCloseableDetails.java
class Reporter implements AutoCloseable {
String name = getClass().getSimpleName();
Reporter() {
System.out.println("Creating " + name);
}
public void close() {
System.out.println("Closing " + name);
}
}
class First extends Reporter {}
class Second extends Reporter {}
public class AutoCloseableDetails {
public static void main(String[] args) {
try(
First f = new First();
Second s = new Second()
) {
}
}
}
输出为:
Creating First
Creating Second
Closing Second
Closing First
退出
假设我们在资源规范头中定义了一个不是
// exceptions/TryAnything.java
// {WillNotCompile}
class Anything {}
public class TryAnything {
public static void main(String[] args) {
try(
Anything a = new Anything()
) {
}
}
}
正如我们所希望和期望的那样,
如果其中一个构造函数抛出异常怎么办?
// exceptions/ConstructorException.java
class CE extends Exception {}
class SecondExcept extends Reporter {
SecondExcept() throws CE {
super();
throw new CE();
}
}
public class ConstructorException {
public static void main(String[] args) {
try(
First f = new First();
SecondExcept s = new SecondExcept();
Second s2 = new Second()
) {
System.out.println("In body");
} catch(CE e) {
System.out.println("Caught: " + e);
}
}
}
输出为:
Creating First
Creating SecondExcept
Closing First
Caught: CE
现在资源规范头中定义了
正如预期的那样,
如果没有构造函数抛出异常,但在
// exceptions/BodyException.java
class Third extends Reporter {}
public class BodyException {
public static void main(String[] args) {
try(
First f = new First();
Second s2 = new Second()
) {
System.out.println("In body");
Third t = new Third();
new SecondExcept();
System.out.println("End of body");
} catch(CE e) {
System.out.println("Caught: " + e);
}
}
}
输出为:
Creating First
Creating Second
In body
Creating Third
Creating SecondExcept
Closing Second
Closing First
Caught: CE
请注意,第
最后,让我们看一下抛出异常的
// exceptions/CloseExceptions.java
class CloseException extends Exception {}
class Reporter2 implements AutoCloseable {
String name = getClass().getSimpleName();
Reporter2() {
System.out.println("Creating " + name);
}
public void close() throws CloseException {
System.out.println("Closing " + name);
}
}
class Closer extends Reporter2 {
@Override
public void close() throws CloseException {
super.close();
throw new CloseException();
}
}
public class CloseExceptions {
public static void main(String[] args) {
try(
First f = new First();
Closer c = new Closer();
Second s = new Second()
) {
System.out.println("In body");
} catch(CloseException e) {
System.out.println("Caught: " + e);
}
}
}
输出为:
Creating First
Creating Closer
Creating Second
In body
Closing Second
Closing Closer
Closing First
Caught: CloseException
从技术上讲,我们并没有被迫在这里提供一个
请注意,因为所有三个对象都已创建,所以它们都以相反的顺序关闭
异常匹配
抛出异常的时候,异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后,它就认为异常将得到处理,然后就不再继续查找。
查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序,就像这样:
// exceptions/Human.java
// Catching exception hierarchies
class Annoyance extends Exception {}
class Sneeze extends Annoyance {}
public class Human {
public static void main(String[] args) {
// Catch the exact type:
try {
throw new Sneeze();
} catch(Sneeze s) {
System.out.println("Caught Sneeze");
} catch(Annoyance a) {
System.out.println("Caught Annoyance");
}
// Catch the base type:
try {
throw new Sneeze();
} catch(Annoyance a) {
System.out.println("Caught Annoyance");
}
}
}
输出为:
Caught Sneeze
Caught Annoyance
如果把捕获基类的
try {
throw new Sneeze();
} catch(Annoyance a) {
// ...
} catch(Sneeze s) {
// ...
}
此时,编译器会发现
其他可选方式
异常处理系统就像一个活门(trap door
异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常
“被检查的异常”使这个问题变得有些复杂,因为它们强制你在可能还没准备好处理错误的时候被迫加上
try {
// ... to do something useful
} catch(ObligatoryException e) {} // Gulp!
程序员们只做最简单的事情(包括我自己,在本书第
当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第
这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是
历史
异常处理起源于
为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架,异常处理机制很晚才被加入
“…
. 每次调用的时候都必须执行条件测试,以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率,因此程序员们既不愿意指出,也不愿意处理异常。 ”
因此,异常处理的初衷是要消除这种限制,但是我们又从
“…
. 要求程序员把异常处理程序的代码文本附接到会引发异常的调用上,这会降低程序的可读性,使得程序的正常思路被异常处理给破坏了。 ”
值得注意的是,由于使用了模板,
观点
首先,值得注意的是
其次,仅从示意性的例子和小程序来看
看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名
“仅从小程序来看,会认为异常说明能增加开发人员的效率,并提高代码的质量;但考察大项目的时候,结论就不同了
- 开发效率下降了,而代码质量只有微不足道的提高,甚至毫无提高”。
谈到未被捕获的异常的时候,
“我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序,是不现实的
。 ”
在解释为什么“函数没有异常说明就表示可以抛出任何异常”的时候,
“但是,这样一来几乎所有的函数都得提供异常说明了,也就都得重新编译,而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束,他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感
。 ”
我们已经看到这种破坏异常机制的行为了
Martin Fowler(UML Distilled,
“…总体来说,我觉得异常很不错,但是
Java 的”被检查的异常“带来的麻烦比好处要多。 ”
我现在认为
过去,我曾坚定地认为“被检查的异常”和强静态类型检查对开发健壮的程序是非常必要的。但是,我看到的以及我使用一些动态(类型检查)语言的亲身经历告诉我,这些好处实际上是来自于:
- 不在于编译器是否会强制程序员去处理错误,而是要有一致的、使用异常来报告错误的模型。
- 不在于什么时候进行检查,而是一定要有类型检查。也就是说,必须强制程序使用正确的类型,至于这种强制施加于编译时还是运行时,那倒没关系。
此外,减少编译时施加的约束能显著提高程序员的编程效率。事实上,反射和泛型就是用来补偿静态类型检查所带来的过多限制,在本书很多例子中都会见到这种情形。
我已经听到有人在指责了,他们认为这种言论会令我名誉扫地,会让文明堕落,会导致更高比例的项目失败。他们的信念是应该在编译时指出所有错误,这样才能挽救项目,这种信念可以说是无比坚定的;其实更重要的是要理解编译器的能力限制。在 http://MindView.net/Books/BetterJava 上的补充材料中,我强调了自动构建过程和单元测试的重要性,比起把所有的东西都说成是语法错误,它们的效果可以说是事半功倍。下面这段话是至理名言:
好的程序设计语言能帮助程序员写出好程序,但无论哪种语言都避免不了程序员用它写出坏程序。
不管怎么说,要让
把异常传递给控制台
在简单的程序中,不用写多少代码就能保留异常的最简单的方法,就是把它们从 main()
传递到控制台。例如,为了读取信息而打开一个文件(在文件 章节中将详细介绍FilelnputStream
进行打开和关闭操作,这就可能会产生异常。对于简单的程序,可以像这样做(本书中很多地方采用了这种方法
// exceptions/MainException.java
import java.util.*;
import java.nio.file.*;
public class MainException {
// Pass exceptions to the console:
public static void main(String[] args) throws Exception {
// Open the file:
List<String> lines = Files.readAllLines(
Paths.get("MainException.java"));
// Use the file ...
}
}
注意,
把“被检查的异常”转换为“不检查的异常”
当编写自己使用的简单程序时,从 main()
中抛出异常是很方便的,但这并不总是有用。真正的问题是,当在一个普通方法里调用别的方法时发现RuntimeException
的构造器,从而将它包装进 RuntimeException
里,就像这样:
try {
// ... to do something useful
} catch(IDontKnowWhatToDoWithThisCheckedException e) {
throw new RuntimeException(e);
}
如果想把“被检查的异常”这种功能“屏蔽”掉的话,这看上去像是一个好办法。不用“吞下”异常,也不必把它放到方法的异常说明里面,而异常链还能保证你不会丢失任何原始异常的信息。
这种技巧给了你一种选择,你可以不写
// exceptions/TurnOffChecking.java
// "Turning off" Checked exceptions
import java.io.*;
class WrapCheckedException {
void throwRuntimeException(int type) {
try {
switch(type) {
case 0: throw new FileNotFoundException();
case 1: throw new IOException();
case 2: throw new
RuntimeException("Where am I?");
default: return;
}
} catch(IOException | RuntimeException e) {
// Adapt to unchecked:
throw new RuntimeException(e);
}
}
}
class SomeOtherException extends Exception {}
public class TurnOffChecking {
public static void main(String[] args) {
WrapCheckedException wce =
new WrapCheckedException();
// You can call throwRuntimeException() without
// a try block, and let RuntimeExceptions
// leave the method:
wce.throwRuntimeException(3);
// Or you can choose to catch exceptions:
for(int i = 0; i < 4; i++)
try {
if(i < 3)
wce.throwRuntimeException(i);
else
throw new SomeOtherException();
} catch(SomeOtherException e) {
System.out.println(
"SomeOtherException: " + e);
} catch(RuntimeException re) {
try {
throw re.getCause();
} catch(FileNotFoundException e) {
System.out.println(
"FileNotFoundException: " + e);
} catch(IOException e) {
System.out.println("IOException: " + e);
} catch(Throwable e) {
System.out.println("Throwable: " + e);
}
}
}
}
输出为:
FileNotFoundException: java.io.FileNotFoundException
IOException: java.io.IOException
Throwable: java.lang.RuntimeException: Where am I?
SomeOtherException: SomeOtherException
WrapCheckedException.throwRuntimeException()
包含可生成不同类型异常的代码。这些异常被捕获并包装进RuntimeException
对象,所以它们成了这些运行时异常的原因(“cause”
在
这种把被检查的异常用 RuntimeException
包装起来的技术,将在本书余下部分使用。另一种解决方案是创建自己的 RuntimeException
的子类。这样的话,异常捕获将不被强制要求,但是任何人都可以在需要的时候捕获这些异常。
异常指南
应该在下列情况下使用异常:
- 尽可能使用
try-with-resource 。 - 在恰当的级别处理问题
。 (在知道该如何处理的情况下才捕获异常。 ) - 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
- 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
- 终止程序。
- 进行简化
。 (如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。 ) - 让类库和程序更安全
。 (这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。 )
本章小结
异常是
异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于
就像你将要在后续章节中看到的,通过将这个问题甩给其他代码
后记:Exception Bizarro World
(来自于
我的朋友finally
块中,他也不得不放入更多的 try-catch
子句,因为关闭连接也会导致异常。这些麻烦事的终点在哪里?本来只是做一些简单的事,但却被强制要求在一个个环里跳来跳去(注意,
我们开始讨论
我的印象是
他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。
替代方案非常简单,起初它几乎看起来像
result, err := functionCall()
( :=
告诉result
和 err
,并且推断它们的类型)
就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步
起初这看起来很原始,是向“古代”的回归。但到目前为止,我发现
它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进
关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。
跳入奇异的世界会给