Error Handling with Exceptions【2】

The keyword throw causes a number of relatively magical things to happen. Typically, you’ll first use new to create an object that represents the error condition. You give the resulting reference to throw. The object is, in effect, “returned” from the method, even though that object type isn’t normally what the method is designed to return. A simplistic way to think about exception handling is as a different kind of return mechanism, although you get into trouble if you take that analogy too far. You can also exit from ordinary scopes by throwing an exception. But a value is returned, and the method or scope exits.

Throw關鍵詞引發了一系列相關的事情,典型的,你首先會用new方法創建一個對象來描述異常的條件,將這個對象給了throw方法。這個方法會返回這個對象,儘管它的類型不是這個方法設計所返回的類型。一種簡單的方式來看“異常處理程序”就是一種不同返回類型的機制,但是這種機制不要走的太遠否則就麻煩了,你也可以在一段順序執行的區域採用throw 一個異常來退出程序。但是返回結果,退出了方法或者作用域。

Any similarity to an ordinary return from a method ends here, because where you return is someplace completely different from where you return for a normal method call. (You end up in an appropriate exception handler that might be far—many levels away on the call stack—from where the exception was thrown.)

異常處理和正常程序的返回相同點就是這些了。因爲你拋出異常的地方和你返回正常發發調用的地方有着本質的區別。你解決這個異常信息的地方可能距離異常的拋出地方很遠。

In addition, you can throw any type of Throwable (the exception root class) object that you want. Typically, you’ll throw a different class of exception for each different type of error. The information about the error is represented both inside the exception object and implicitly in the name of the exception class, so someone in the bigger context can figure out what to do with your exception. (Often, the only information is the type of exception, and nothing meaningful is stored within the exception object.)

另外,你可以拋出任何類型的以Throwable爲基類的對象。典型的,你會根據不同的錯誤拋出不同類型的異常信息。關於錯誤的信息的描述在對象裏並且暗含在類的名字當中,所以在更高的環境中可以識別出根據異常信息如何處理。(通常,有用的信息就是這個對象的類型,而在對象中存儲的信息沒有什麼意義)

Catching an exception

If a method throws an exception, it must assume that exception will be “caught” and dealt with. One of the advantages of exception handling is that it allows you to concentrate on the problem you’re trying to solve in one place, and then deal with the errors from that code in another place.

如果一個方法拋出了一個異常,它必須確保這個異常信息可以被捕獲和處理掉。“異常處理程序”的優勢之一就是它允許你在一個地方集中精力解決這個問題,而到另外一個地方來解決這個異常。

To see how an exception is caught, you must first understand the concept of a guarded region. This is a section of code that might produce exceptions and is followed by the code to handle those exceptions.

要想知道如何捕獲異常,你必須首先的知道被保護區的概念。這是一段可能會產生異常信息的代碼區,緊接着下面的代碼就是處理這個異常信息的。

The try block

If you’re inside a method and you throw an exception (or another method you call within this method throws an exception), that method will exit in the process of throwing. If you don’t want a throw to exit the method, you can set up a special block within that method to capture the exception. This is called the try block because you “try” your various method calls there. The try block is an ordinary scope preceded by the keyword try:

如果你在一個方法內部拋出了一個異常信息(或者你調用的一個方法拋出一個異常),這個方法拋出異常的時候就會退出執行。如果你不希望不拋出一個退出的方法,你可以建立一個特殊的塊來捕獲這個異常信息。這被叫做“Try”區,因爲你在這裏Try各種方法的調用。這個區域只是緊緊跟隨關鍵詞Try的一段普通代碼區。

try {

  // Code that might generate exceptions

}

If you were checking for errors carefully in a programming language that didn’t support exception handling, you’d have to surround every method call with setup and error testing code, even if you call the same method several times. With exception handling, you put everything in a try block and capture all the exceptions in one place. This means your code is much easier to write and read because the goal of the code is not confused with the error checking.

你必須爲了不支持異常處理程序的語言仔細的檢查錯誤信息,你必須在每個方法周圍寫很多測試錯誤的代碼,甚至你調用一個方法很多次。而有了異常處理程序之後,你把所有的程序放置到Try Block,再把捕獲異常信息的代碼放到一個位置。這將使得你的代碼書寫更加簡單,這樣的話你的邏輯代碼和錯誤檢查代碼就不會很複雜的交織在一起。

Exception handlers

Of course, the thrown exception must end up someplace. This “place” is the exception handler, and there’s one for every exception type you want to catch. Exception handlers immediately follow the try block and are denoted by the keyword catch:

當然,拋出異常也必須在某些位置結束。而這個位置就是“異常處理程序”,這裏你可以書寫你希望捕獲的所有類型的異常信息。異常處理程序緊緊跟隨Try Block區並且以關鍵詞Catch來標識。

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...

Each catch clause (exception handler) is like a little method that takes one and only one argument of a particular type. The identifier (id1, id2, and so on) can be used inside the handler, just like a method argument. Sometimes you never use the identifier because the type of the exception gives you enough information to deal with the exception, but the identifier must still be there.

每個catch的句子都更像是一個小的方法,並且都擁有一個特定的類型參數。這些標識符可以在異常處理程序中使用,就像參數一樣使用。有時候你可能根本用不到這些標識符因爲他異常的類型已經提供了足夠的信息來處理這個異常,但是你仍然要保留這個參數。

The handlers must appear directly after the try block. If an exception is thrown, the exception handling mechanism goes hunting for the first handler with an argument that matches the type of the exception. Then it enters that catch clause, and the exception is considered handled. The search for handlers stops once the catch clause is finished. Only the matching catch clause executes; it’s not like a switch statement in which you need a break after each case to prevent the remaining ones from executing.

異常處理模塊必須直接出現在Try Block區之後。如果拋出了一個異常信息,那麼異常處理機制會搜索第一個參數與異常類型匹配的處理程序。然後進入異常處理語句,這個異常就被認爲得到了處理。當異常處理結束的時候搜索匹配的程序也就停止了。只有匹配的異常處理部分得到執行。它不像是switch語句還需要在每個case中使用break來阻止剩餘部分繼續執行。

Note that within the try block, a number of different method calls might generate the same exception, but you need only one handler.

需要說明的是在Try block,不同的方法可能會產生一個同樣的異常信息,但是我們只需要處理一個就足夠了。

Termination vs. resumption

There are two basic models in exception handling theory. In termination (which is what Java and C++ support), you assume that the error is so critical that there’s no way to get back to where the exception occurred. Whoever threw the exception decided that there was no way to salvage the situation, and they don’t want to come back.

在異常處理理論中有兩個基本的模型。在terminationjavaC++支持),你認爲這個異常信息太嚴重了沒有辦法回退到異常拋出的地方了。拋出這個異常則意味着沒有辦法可以解決,並且希望這個異常不要再回來了。

The alternative is called resumption. It means that the exception handler is expected to do something to rectify the situation, and then the faulting method is retried, presuming success the second time. If you want resumption, it means you still hope to continue execution after the exception is handled. In this case, your exception is more like a method call—which is how you should set up situations in Java in which you want resumption-like behavior. (That is, don’t throw an exception; call a method that fixes the problem.) Alternatively, place your try block inside a while loop that keeps reentering the try block until the result is satisfactory.

另外一個被稱爲繼續。它的意思是異常處理程序希望作某些工作來調整當前的情形,然後再執行出錯的地方,希望第二次可以成功。如果你希望繼續執行,那就是說你希望異常被處理之後還繼續執行。在這種情況下,你的異常更像是方法調用-如果你想在Java中實現這種效果可以用這種辦法來設置環境(也就是說不拋出吟唱而是調用一個方法來修補這個問題),

還有一種辦法將你的try block區在得到滿意的結果之前一直進入try block區。

Historically, programmers using operating systems that supported resumptive exception handling eventually ended up using termination-like code and skipping resumption. So although resumption sounds attractive at first, it isn’t quite so useful in practice. The dominant reason is probably the coupling that results; your handler must often be aware of where the exception is thrown, and contain nongeneric code specific to the throwing location. This makes the code difficult to write and maintain, especially for large systems where the exception can be generated from many points.

長久以來,程序員們都是一直使用支持繼續模式的異常處理的操作系統,但是自己的代碼確實一直使用終止模式來跳過繼續執行。所以雖然繼續模式聽起來很吸引人,但是在實際當中還是很少使用的。最主要的原因還是擔心它所導致的耦合。你的異常處理程序意識到哪裏會拋出異常信息,並且需要泛型的代碼來標識出異常拋出的地點。這使得代碼非常難寫和維護,尤其是對於那些很大的系統會有很多的地方拋出異常。

Creating your own exceptions

You’re not stuck using the existing Java exceptions. The JDK exception hierarchy can’t foresee all the errors you might want to report, so you can create your own to denote a special problem that your library might encounter.

你應該不介意使用Java已經存在的異常信息。JDK的異常關係統並不能預知所有的錯誤你都想拋出,所以你可以建立異常類,來描述在自己的類庫中可能遇到的問題。

To create your own exception class, you must inherit from an existing exception class, preferably one that is close in meaning to your new exception (although this is often not possible). The most trivial way to create a new type of exception is just to let the compiler create the default constructor for you, so it requires almost no code at all:

創建一個自己的異常類,你必須繼承一個已經存在的異常類,最好還在意思上和你要建立的異常類相近(雖然一般來講是不太可能的)。創建新異常類的最簡單的辦法就是讓編譯器爲你創建一個默認的構造方法,這樣你幾乎就不需要寫任何代碼了。

import com.bruceeckel.simpletest.*;

 

class SimpleException extends Exception {}

 

public class SimpleExceptionDemo {

  private static Test monitor = new Test();

  public void f() throws SimpleException {

    System.out.println("Throw SimpleException from f()");

    throw new SimpleException();

  }

  public static void main(String[] args) {

    SimpleExceptionDemo sed = new SimpleExceptionDemo();

    try {

      sed.f();

    } catch(SimpleException e) {

      System.err.println("Caught it!");

    }

    monitor.expect(new String[] {

      "Throw SimpleException from f()",

      "Caught it!"

    });

  }

}

 

The compiler creates a default constructor, which automatically (and invisibly) calls the base-class default constructor. Of course, in this case you don’t get a SimpleException(String) constructor, but in practice that isn’t used much. As you’ll see, the most important thing about an exception is the class name, so most of the time an exception like the one shown here is satisfactory.

編譯器自動的創建了一個構造方法,這個方法是不可見的,它會調用基類的構造方法。當然,這樣你就沒有SimpleException(String)的構造方法,但是在實際中這並不常見。就像你看到的最重要的是異常的類名,所以大部分時間這個異常也就足夠了。

Here, the result is printed to the console standard error stream by writing to System.err. This is usually a better place to send error information than System.out, which may be redirected. If you send output to System.err, it will not be redirected along with System.out so the user is more likely to notice it.

在這裏System.err會把錯誤信息打印到控制檯的standard error stream。這比將錯誤信息發送到System.out要好的多,因爲後者可能會重新定向。如果你發送錯誤消息到System.err,它就不會被重新定向,這樣就能更好的引起用戶的注意。

You can also create an exception class that has a constructor with a String argument:

你也可以創建一個帶有String參數的異常類

import com.bruceeckel.simpletest.*;

 

class MyException extends Exception {

  public MyException() {}

  public MyException(String msg) { super(msg); }

}

 

public class FullConstructors {

  private static Test monitor = new Test();

  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();

    }

    try {

      g();

    } catch(MyException e) {

      e.printStackTrace();

    }

    monitor.expect(new String[] {

      "Throwing MyException from f()",

      "MyException",

      "%% /tat FullConstructors.f//(.*//)",

      "%% /tat FullConstructors.main//(.*//)",

      "Throwing MyException from g()",

      "MyException: Originated in g()",

      "%% /tat FullConstructors.g//(.*//)",

      "%% /tat FullConstructors.main//(.*//)"

    });

  }

}

The added code is small: two constructors that define the way MyException is created. In the second constructor, the base-class constructor with a String argument is explicitly invoked by using the super keyword.

添加了很少的代碼,MyException類裏面擁有了兩個構造方法。在第二個構造方法中,基類中有String參數的方法也使用關鍵詞super得到了調用。

In the handlers, one of the Throwable (from which Exception is inherited) methods is called: printStackTrace( ). This produces information about the sequence of methods that were called to get to the point where the exception happened. By default, the information goes to the standard error stream, but overloaded versions allow you to send the results to any other stream as well.

在異常處理程序中,調用了ThrowableException是從那裏繼承而來的)類中的printStackTrace()方法。它會產生一個關於“被調用的方法經歷了一個怎樣的順序到達了錯誤的發生地點”,默認的這個錯誤信息會被送到標準的錯誤信息流,但是重載厚的版本可能允許你把它送到其它的信息流。

The process of creating your own exceptions can be taken further. You can add extra constructors and members:

創建自己的異常類的過程可以走的更遠,你可以增加一些額外的構造方法和成員。

import com.bruceeckel.simpletest.*;

 

class MyException2 extends Exception {

  private int x;

  public MyException2() {}

  public MyException2(String msg) { super(msg); }

  public MyException2(String msg, int x) {

    super(msg);

    this.x = x;

  }

  public int val() { return x; }

  public String getMessage() {

    return "Detail Message: "+ x + " "+ super.getMessage();

  }

}

 

public class ExtraFeatures {

  private static Test monitor = new Test();

  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();

    }

    try {

      g();

    } catch(MyException2 e) {

      e.printStackTrace();

    }

    try {

      h();

    } catch(MyException2 e) {

      e.printStackTrace();

      System.err.println("e.val() = " + e.val());

    }

    monitor.expect(new String[] {

      "Throwing MyException2 from f()",

      "MyException2: Detail Message: 0 null",

      "%% /tat ExtraFeatures.f//(.*//)",

      "%% /tat ExtraFeatures.main//(.*//)",

      "Throwing MyException2 from g()",

      "MyException2: Detail Message: 0 Originated in g()",

      "%% /tat ExtraFeatures.g//(.*//)",

      "%% /tat ExtraFeatures.main//(.*//)",

      "Throwing MyException2 from h()",

      "MyException2: Detail Message: 47 Originated in h()",

      "%% /tat ExtraFeatures.h//(.*//)",

      "%% /tat ExtraFeatures.main//(.*//)",

      "e.val() = 47"

    });

  }

} 

A field i has been added, along with a method that reads that value and an additional constructor that sets it. In addition, Throwable.getMessage( ) has been overridden to produce a more interesting detail message. getMessage( ) is something like toString( ) for exception classes.

增加了i數據成員,並且有一個方法讀取這個值,另外的一個構造方法還有一個方法在構造方法中對其進行初始化。還有,Throwable.getMessage()被覆寫爲產生很有意思的詳細信息了。這個方法在exception類中更像是toString()方法。

Since an exception is just another kind of object, you can continue this process of embellishing the power of your exception classes. Keep in mind, however, that all this dressing-up might be lost on the client programmers using your packages, since they might simply look for the exception to be thrown and nothing more. (That’s the way most of the Java library exceptions are used.)

因爲異常和其它的對象沒有什麼區別,所以你可以繼續修飾這個Exception類。但是記住,你修飾的這些在客戶端程序員面前很可能丟失掉,因爲他們僅僅是用它來拋出一個異常信息而已其它的什麼都不需要。JavaException類庫大部分都是這麼使用的。

The exception specification

In Java, you’re encouraged to inform the client programmer, who calls your method, of the exceptions that might be thrown from your method. This is civilized, because the caller can know exactly what code to write to catch all potential exceptions. Of course, if source code is available, the client programmer could hunt through and look for throw statements, but often a library doesn’t come with sources. To prevent this from being a problem, Java provides syntax (and forces you to use that syntax) to allow you to politely tell the client programmer what exceptions this method throws, so the client programmer can handle them. This is the exception specification and it’s part of the method declaration, appearing after the argument list.

Java中,鼓勵你告訴那些調用你的方法的客戶端程序員們,在你的方法中可能會拋出異常。這是很明智的做法,因爲只有這樣才知道如何書寫代碼來捕獲那些潛在的異常信息。當然如果有源代碼的話,客戶端程序員可以查找源代碼來尋找throw拋出異常的語句,但是一個類庫通常是不提供源碼的。要防止這個稱爲一個問題,Java提供了一種語法並且強制要求你使用的語法,可以讓你很客氣的告訴程序員這個方法會拋出什麼類型的異常信息,這樣客戶端程序員就可以捕獲它們。這就是異常的語法,它經常出現在方法的聲明部分並且緊跟在參數的後面。

The exception specification uses an additional keyword, throws, followed by a list of all the potential exception types. So your method definition might look like this:

異常語法使用了一個其它的關鍵詞throws,後面緊接着是一些可能會拋出的異常的類型,所以你的方法定義可能像下面這樣了:

void f() throws TooBig, TooSmall, DivZero { //... 

If you say

void f() { // ...

it means that no exceptions are thrown from the method (except for the exceptions inherited from RuntimeException, which can be thrown anywhere without exception specifications—this will be described later).

這的意思是這個方法不會拋出任何異常(除了從RuntimeException繼承下來的異常信息,因爲這個異常不需要聲明就可以拋出來,這個我們稍後會講到)。

You can’t lie about an exception specification. If the code within your method causes exceptions, but your method doesn’t handle them, the compiler will detect this and tell you that you must either handle the exception or indicate with an exception specification that it may be thrown from your method. By enforcing exception specifications from top to bottom, Java guarantees that a certain level of exception correctness can be ensured at compile time.

不要對異常說明說謊。如果你的代碼發生了一個異常,而你沒有去捕獲它們,編譯器會發現它並且告訴你要麼捕獲這個異常要麼在異常說明部分提示這個方法會拋出異常。通過強化這種從頂層到底層的異常說明,Java在一定程序上在編譯期就可以糾正你的異常。

There is one place you can lie: You can claim to throw an exception that you really don’t. The compiler takes your word for it, and forces the users of your method to treat it as if it really does throw that exception. This has the beneficial effect of being a placeholder for that exception, so you can actually start throwing the exception later without requiring changes to existing code. It’s also important for creating abstract base classes and interfaces whose derived classes or implementations may need to throw exceptions.

那是不是任何地方你都不能說謊了呢?你可以在根本不會拋出異常的地方要求拋出異常,編譯器會當真的,它會強制要求使用這個方法的用戶像處理這個真的會拋出異常一樣處理。這麼作的好處就是它先爲異常佔據了一個位置,當你真的想要拋出異常的時候不需要修改任何代碼。這對於創建抽象基類和接口也很重要,因爲它們的派生類和實現很可能需要拋出異常信息。

Exceptions that are checked and enforced at compile time are called checked exceptions.

會在編譯期間檢查並且得到處理的異常叫做checked exceptions

Catching any exception

It is possible to create a handler that catches any type of exception. You do this by catching the base-class exception type Exception (there are other types of base exceptions, but Exception is the base that’s pertinent to virtually all programming activities):

catch(Exception e) {

  System.err.println("Caught an exception");

}

創建一個能夠處理任何類型的異常的異常處理程序是完全可行的。你可以通過捕獲Exception的基類來完成,還有其它關於Exception的基類,但是這個和編程關係最相關。

This will catch any exception, so if you use it you’ll want to put it at the end of your list of handlers to avoid preempting any exception handlers that might otherwise follow it.

這個能夠捕獲任何異常信息,所以如果你使用的話你需要把它放到所有的異常類型的最後,避免它在其它更詳盡的異常前面把異常拋出。

Since the Exception class is the base of all the exception classes that are important to the programmer, you don’t get much specific information about the exception, but you can call the methods that come from its base type Throwable:

因爲Exception是所有異常信息的基類,所以這個對於程序員來講是很重要的,你沒能得到更多的關於exception的信息,但是你可以調用它的基類Throwable的方法。

String getMessage( )
String getLocalizedMessage( )
Gets the detail message, or a message adjusted for this particular locale.

String toString( )
Returns a short description of the Throwable, including the detail message if there is one.

void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
Prints the Throwable and the Throwable’s call stack trace. The call stack shows the sequence of method calls that brought you to the point at which the exception was thrown. The first version prints to standard error, the second and third prints to a stream of your choice (in Chapter 12, you’ll understand why there are two types of streams).

Throwable fillInStackTrace( )
Records information within this Throwable object about the current state of the stack frames. Useful when an application is rethrowing an error or exception (more about this shortly).

In addition, you get some other methods from Throwable’s base type Object (everybody’s base type). The one that might come in handy for exceptions is getClass( ), which returns an object representing the class of this object. You can in turn query this Class object for its name with getName( ). You can also do more sophisticated things with Class objects that aren’t necessary in exception handling.

還有,你可以調用Throwable的基類Object的一些方法。有一個方法是很有用的,getClass(),它返回了這個對象所屬的類的描述,你可以使用getName()查詢這個類型的對象的名字,當然你還可以在Object類得到更多有用的東西,但是對異常處理就不是很關鍵了。

Here’s an example that shows the use of the basic Exception methods:

下面給出一個例子來展示基本的Exception的方法:

import com.bruceeckel.simpletest.*;

 

public class ExceptionMethods {

  private static Test monitor = new Test();

  public static void main(String[] args) {

    try {

      throw new Exception("My Exception");

    } catch(Exception e) {

      System.err.println("Caught Exception");

      System.err.println("getMessage():" + e.getMessage());

      System.err.println("getLocalizedMessage():" +

        e.getLocalizedMessage());

      System.err.println("toString():" + e);

      System.err.println("printStackTrace():");

      e.printStackTrace();

    }

    monitor.expect(new String[] {

      "Caught Exception",

      "getMessage():My Exception",

      "getLocalizedMessage():My Exception",

      "toString():java.lang.Exception: My Exception",

      "printStackTrace():",

      "java.lang.Exception: My Exception",

      "%% /tat ExceptionMethods.main//(.*//)"

    });

  }

} 

You can see that the methods provide successively more information—each is effectively a superset of the previous one.

你能看到這些方法一個比一個提供了更多的信息,其實它們每一個都是前面一個的超集。

Rethrowing an exception

Sometimes you’ll want to rethrow the exception that you just caught, particularly when you use Exception to catch any exception. Since you already have the reference to the current exception, you can simply rethrow that reference:

典型來說,你使用Exception捕獲了所有的異常信息,有時候你可能會想把剛纔捕獲的異常信息再拋出去,因爲你已經獲得了這個異常的對象,所以可以很簡單的把這個對象再拋出去。

catch(Exception e) {

  System.err.println("An exception was thrown");

  throw e;

}

Rethrowing an exception causes it to go to the exception handlers in the next-higher context. Any further catch clauses for the same try block are still ignored. In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type can extract all the information from that object.

重拋異常信息將這個異常跑出到一個更外層的環境中,所有針對於try語句中的catch語句將都被忽略。當然異常對象中的所有信息都得到了報出,所以當外層捕獲這個異常信息的時候就能分解出這個對象的所有信息了。

If you simply rethrow the current exception, the information that you print about that exception in printStackTrace( ) will pertain to the exception’s origin, not the place where you rethrow it. If you want to install new stack trace information, you can do so by calling fillInStackTrace( ), which returns a Throwable object that it creates by stuffing the current stack information into the old exception object. Here’s what it looks like:

如果你只是簡單的拋出異常信息,關於異常對象的信息你可以使用printStackTrace()打印出來,它指向的是拋出異常的原始位置而不是再次拋出異常的位置。如果你還希望轉載一些棧軌跡信息,你可以使用fillInStackTrace()方法,這個方法會將當前的棧信息寫進就的異常對象中並返回一個Throwable的對象,下面就是它的工作方式:

import com.bruceeckel.simpletest.*;

 

public class Rethrowing {

  private static Test monitor = new Test();

  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 Throwable {

    try {

      f();

    } catch(Exception e) {

      System.err.println("Inside g(),e.printStackTrace()");

      e.printStackTrace();

      throw e; //17

      //throw e.fillInStackTrace(); 18

    }

  }

  public static void  main(String[] args) throws Throwable {

    try {

      g();

    } catch(Exception e) {

      System.err.println(

        "Caught in main, e.printStackTrace()");

      e.printStackTrace();

    }

    monitor.expect(new String[] {

      "originating the exception in f()",

      "Inside g(),e.printStackTrace()",

      "java.lang.Exception: thrown from f()",

      "%% /tat Rethrowing.f(.*?)",

      "%% /tat Rethrowing.g(.*?)",

      "%% /tat Rethrowing.main(.*?)",

      "Caught in main, e.printStackTrace()",

      "java.lang.Exception: thrown from f()",

      "%% /tat Rethrowing.f(.*?)",

      "%% /tat Rethrowing.g(.*?)",

      "%% /tat Rethrowing.main(.*?)"

    });

  }

} 

The important line numbers are marked as comments. With line 17 uncommented (as shown), the output is as shown, so the exception stack trace always remembers its true point of origin no matter how many times it gets rethrown.

最重要的被標識爲註釋了,而第17行代碼我們看到了輸出結果,所以這個異常信息始終記得它被拋出的原始位置,不管被拋出了多少次

With line 17 commented and line 18 uncommented, fillInStackTrace( ) is used instead, and the result is:

如果將第17行註釋掉而把18行執行,那麼fillInStackTrace()會被執行,結果就是:

originating the exception in f()

Inside g(),e.printStackTrace()

java.lang.Exception: thrown from f()

        at Rethrowing.f(Rethrowing.java:9)

        at Rethrowing.g(Rethrowing.java:12)

        at Rethrowing.main(Rethrowing.java:23)

Caught in main, e.printStackTrace()

java.lang.Exception: thrown from f()

        at Rethrowing.g(Rethrowing.java:18)

        at Rethrowing.main(Rethrowing.java:23)

(Plus additional complaints from the Test.expect( ) method.) Because of fillInStackTrace( ), line 18 becomes the new point of origin of the exception.

加上一些Test.expect()方法的錯誤信息加上finllInStackTrace(),第18行就稱爲了新的異常的拋出地了。

The class Throwable must appear in the exception specification for g( ) and main( ) because fillInStackTrace( ) produces a reference to a Throwable object. Since Throwable is a base class of Exception, it’s possible to get an object that’s a Throwable but not an Exception, so the handler for Exception in main( ) might miss it. To make sure everything is in order, the compiler forces an exception specification for Throwable. For example, the exception in the following program is not caught in main( ):

Throwable類必須要出現在g()main()方法中,因爲fillInStackTrace()方法返回了一個Throwable的對象。因爲ThrowableException的基類,所以在main方法中捕獲Exception異常則可能會捕獲不到,爲了確保程序的有序執行,編譯器強制要求在異常說明中使用Throwable。舉例來講,下面的程序中main方法就沒有捕獲到exception信息。

public class ThrowOut {

  public static void

  main(String[] args) throws Throwable {

    try {

      throw new Throwable();

    } catch(Exception e) {

      System.err.println("Caught in main()");

    }

  }

}

It’s also possible to rethrow a different exception from the one you caught. If you do this, you get a similar effect as when you use fillInStackTrace( )—the information about the original site of the exception is lost, and what you’re left with is the information pertaining to the new throw:

在你捕獲異常的地方你可以拋出一個新的異常信息也是可以的。如果你這麼做的話,你得到的結果和剛纔調用fillInStackTrace()的效果是一樣的,原始位置發生異常信息都丟失了,所留下的只是異常再次被拋出時異常的信息了。

import com.bruceeckel.simpletest.*;

 

class OneException extends Exception {

  public OneException(String s) { super(s); }

}

 

class TwoException extends Exception {

  public TwoException(String s) { super(s); }

}

 

public class RethrowNew {

  private static Test monitor = new Test();

  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) throws TwoException {

    try {

      f();

    } catch(OneException e) {

      System.err.println(

        "Caught in main, e.printStackTrace()");

      e.printStackTrace();

      throw new TwoException("from main()");

    }

    monitor.expect(new String[] {

      "originating the exception in f()",

      "Caught in main, e.printStackTrace()",

      "OneException: thrown from f()",

      "/tat RethrowNew.f(RethrowNew.java:18)",

      "/tat RethrowNew.main(RethrowNew.java:22)",

      "Exception in thread /"main/" " +

      "TwoException: from main()",

      "/tat RethrowNew.main(RethrowNew.java:28)"

    });

  }

} 

The final exception knows only that it came from main( ) and not from f( ).

最後的異常對象只是知道這個異常來自main(),而不是f();

You never have to worry about cleaning up the previous exception, or any exceptions for that matter. They’re all heap-based objects created with new, so the garbage collector automatically cleans them all up.

你不必擔心清理這些建立的異常對象,或者其它的異常信息。它們都是通過new方法在堆中創建的,所以垃圾回收器會自動的將它們清理掉。

Exception chaining

Often you want to catch one exception and throw another, but still keep the information about the originating exception—this is called exception chaining. Prior to JDK 1.4, programmers had to write their own code to preserve the original exception information, but now all Throwable subclasses may take a cause object in their constructor. The cause is intended to be the originating exception, and by passing it in you maintain the stack trace back to its origin, even though you’re creating and throwing a new exception at this point.

往往我們希望捕獲了一個異常但是拋出了另外一個異常信息,而且希望記錄原始的異常的信息,這叫做“異常鏈”。早在JDK1.4的時候程序員們就不得不去寫代碼來保存原始的異常信息,但是現在所有的Throwable的子類的構造方法都能接受一個cause對象,這個cause就是來保存上一個異常的,這樣通過你維護的棧軌跡就可用回退到異常原始發生的,即便是你在這個位置創建並且拋出了新的異常也可以。

It’s interesting to note that the only Throwable subclasses that provide the cause argument in the constructor are the three fundamental exception classes Error (used by the JVM to report system errors), Exception, and RuntimeException. If you want to chain any other exception types, you do it through the initCause( ) method rather than the constructor.

比較有意思的是Throwable子類中能夠支持cause參數構造方法的一共有三個基礎異常類,分別是:Error(JVM報告系統錯誤使用)ExceptionRuntimeException;如果你要鏈接其它的異常類,那麼你只能使用initCause()方法而不能使用構造方法了。

Here’s an example that allows you to dynamically add fields to a DynamicFields object at run time:

下面給出一個例子,允許你運行時動態的在DynamicFields對象中增加數據項;

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章