C 以及其他早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础之上,而并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志,并且假定接收者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员们在使用程序库的时候更倾向于认为:“对,错误也许会发生,但那是别人造成的,不关我的事”。所以,程序员不去检查错误情形也就不足为奇了(何况对某些错误情形的检查确实很无聊)。如果的确在每次调用方法的时候都彻底地进行错误检查,代码很可能会变得难以阅读。正是由于程序员还仍然用这些方式拼凑系统,所以他们拒绝承认这样一个事实:对于构造大型、健壮、可维护的程序而言,这种错误处理模式已经成为了主要障碍。
当抛出异常后,有几件事会随之发生。首先,同 Java 中其他对象的创建一样,将使用 new 在堆上创建异常对象。然后,当前的执行路径(它不能继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。此时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序能要么换一种方式运行,要么继续运行下去。
// 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!");
}
}
}
// 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)
新增的代码非常简短:两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器,使用 super 关键字明确调用了其基类构造器,它接受一个字符串作为参数。
// 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
___[ 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
新的异常添加了字段 x 以及设定 x 值的构造器和读取数据的方法。此外,还覆盖了 Throwable. getMessage() 方法,以产生更详细的信息。对于异常类来说,getMessage() 方法有点类似于 toString() 方法。
// 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)
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)
最后那个异常仅知道自己来自 main(),而对 f() 一无所知。
永远不必为清理前一个异常对象而担心,或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象,所以垃圾回收器会自动把它们清理掉。
// 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
// 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)
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
}
为了证明 finally 子句总能运行,可以试试下面这个程序:
// 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
// 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);
}
}
}
// 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");
}
}
}
// 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);
}
}
}
// 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
}
}
}
// 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
退出 try 块会调用两个对象的 close() 方法,并以与创建顺序相反的顺序关闭它们。顺序很重要,因为在这种情况下,Second 对象可能依赖于 First 对象,因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。
假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象
// exceptions/TryAnything.java
// {WillNotCompile}
class Anything {}
public class TryAnything {
public static void main(String[] args) {
try(
Anything a = new Anything()
) {
}
}
}
正如我们所希望和期望的那样,Java 不会让我们这样做,并且出现编译时错误。
如果其中一个构造函数抛出异常怎么办?
// 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
这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANSI 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。
C++ 中异常的设计参考了 CLU 方式。Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的,因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是,忽略所有的错误,然后使用调试器来跟踪错误。这些程序员知道,使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此,要把他们拉到“使用错误处理”的正轨上,“多出来的”代码决不能太多。我认为,评价 Java 的“被检查的异常”的时候,这一点是很重要的。
C++ 从 CLU 那里还带来另一种思想:异常说明。这样,就可以用编程的方式在方法签名中声明这个方法将会抛出异常。异常说明有两个目的:一个是“我的代码会产生这种异常,这由你来处理”。另一个是“我的代码忽略了这些异常,这由你来处理”。学习异常处理的机制和语法的时候,我们一直在关注“你来处理”部分,但这里特别值得注意的事实是,我们通常都忽略了异常说明所表达的完整含义。
C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++ 不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++ 会调用标准类库的 unexpected() 函数。
Martin Fowler(UML Distilled,Refactoring 和 Analysis Patterns 的作者)给我写了下面这段话:
“...总体来说,我觉得异常很不错,但是 Java 的”被检查的异常“带来的麻烦比好处要多。”
我现在认为 Java 的重要进步是统一了错误报告模式,所有错误都用异常来报告。这没有在 C++ 中发生,原因是为了向后兼容 C ,直接忽略错误的旧模式在 C++ 中依然是可用的。如果想使用异常,可以始终用异常来报告,如果不想这样做,异常可以抛到最高的级别(比如控制台)。当 Java 修改 C++ 的模式来让异常成为报告错误的唯一方式时,受检查的异常的额外限制可能就变得不那么必要了。
不管怎么说,要让 Java 把“被检查的异常”从语言中去除,这种可能性看来非常渺茫。对语言来说,这个变化可能太激进了点,况且 Sun 的支持者们也非常强大。Sun 有完全向后兼容的历史和策略,实际上所有 Sun 的软件都能在 Sun 的硬件上运行,无论它们有多么古老。然而,如果发现有些“被检查的异常”挡住了路,尤其是发现你不得不去对付那些不知道该如何处理的异常,还是有些办法的。
// 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 ...
}
}
异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实,正是它远远超过如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
我的朋友 James Ward 正在尝试使用 JDBC 创建一些非常简单的教学示例,但不断被受检查的异常所挫败。他把 Howard Lewis Ship 的帖子“被检查的异常的悲剧”指给我看。让 James 尤其沮丧的是,即使做一些本来很简单的事情,也必须在一个个环里跳来跳去。即使在 finally 块中,他也不得不放入更多的 try-catch 子句,因为关闭连接也会导致异常。这些麻烦事的终点在哪里?本来只是做一些简单的事,但却被强制要求在一个个环里跳来跳去(注意,try-with-resources语句可以显著改善这种情况)。
我们开始讨论 Go 编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。
我的印象是Go团队不做任何臆断,只有在明确一个特征是必须的时候才改进语言。他们似乎并不担心做出破坏旧代码的更改 ,因为他们创建了一个重写工具,当做出更改的时候,重写工具将为你重写代码。这使他们将语言变成一个前进的实验,以发现真正需要的东西,而不是做 Big Upfront Design。
跳入奇异的世界会给 James 带来问题,它为所有程序员增加了更多的工作:因为你无法知道什么时候会发生什么事(你可以随时进入奇怪的世界),你必须添加一些 try 块来确保没有任何东西从裂缝中滑落。您最终必须进行额外的编程以补偿异常机制(它似乎类似于补偿共享内存并发所需的额外工作)。
Go 团队采取了大胆的举动,质疑所有这些,并说,“让我们毫无例外地尝试它,看看会发生什么。”是的,这意味着你通常会在发生错误的地方处理错误,而不是最后将它们聚集在一起 try 块。但这也意味着关于一件事的代码是本地化的,也许这并不是那么糟糕。这也可能意味着您无法轻松组合常见的错误处理代码(除非您确定了常用代码并将其放入函数中,也不是那么糟糕)。但这绝对意味着您不必担心有多个可能的执行路径而且所有这些都需要。