l

2011年2月17日 星期四

第 N 度空間

Feb. 17 21:55~23:43

警告!本篇包含怪力亂神之言論,無法默寫出 Java 語言全部的保留字的鄉民們請勿觀看,不聽勸告者觀後若有任何不良影響後果自負。

Teddy 訂了好幾年的科學人(其實是 Kay 訂的,Teddy 只是看免錢的) ,很可惜沒有因此而變得比較科學一點。科學人不是所謂的『科普雜誌』嗎,意思就是『給 死老百姓 普通人看的科學雜誌』,為什麼每期 Teddy 平均大概只看得懂不到 30% 的內容?看來 Teddy 淪落到此 賣笑 搞笑也不是沒有原因滴。

記得有一期科學人在講『宇宙』和『時空』的概念,文中提到科學家為了解釋物理定律,因此提出了什麼五度,六度....N 度空間的概念,因為這些概念在平常我們所熟知的四度空間內不容易(或無法)被處理。在此 Teddy 幫鄉民複習一下,一般所謂的二度空間(平面空間,需要 x, y 來表示),三度空間(立體空間,需要 x, y, z 來表示)四度空間(就是加上時間這個因素)這些都比較容易理解,那到底其他的 五度,六度,七度,八度... N 度空間是怎麼一回事(Teddy 內心獨白:難道好神公仔與阿飄的世界也要各算一度空間?)物理很不強的 Teddy 就無法理解了,不過話說回來,這些物理學家腦袋裡面在想什麼相信不是咱們這些凡夫俗子可以理解的。

最近 Teddy 遇到一個 Java 語言的問題,解完這個問題之後,突然對『某些問題只有在 N 度空間才能夠被解』這件事情有所領悟。話說『某人』想要知道,在 runtime 當 Java 程式收到(catch)一個 exception 的時候,要如何得知該 exception 是從那一個 method 中所丟出來的。以下舉個例子:

public void caller () {
    
    Callee callee = new Callee();

    try {
        callee.test();
        callee.test(100);
        callee.test("this is a test");
      }
       catch(Exception e) {
        // e 是被哪一個 test method 所丟出?
      }
}

public class Callee {

    public void test() throws IOException {
            .....
           throw new IOException();
    }

    public void test(int param) throws IOException {
            .....
           throw new IOException();
    }

    public void test(String param) throws IOException {
            .....
           throw new IOException();
    }
}
***

懂 Java 的鄉民們麻煩動動腦,一起想一下這個問題要怎麼解。乍看之下這個問題很簡單,以下是 Teddy 的學弟利用 Java Reflection 機制所提供的解:

            StackTraceElement element = e.getStackTrace()[0];
            Class aClass = Class.forName(element.getClassName());
            Method method = aClass.getMethod(element.getMethodName());

Java StackTraceElement 物件雖然提供了該 exception 所發生的 method 名稱,以及行號,但是對於 overloaded methods 光有 method name  是不夠的(因為 method names 都一樣啊),還需要有『參數』才能夠判別到底是那一個 overloaded methods。以最前面那個例子而言,光是知道 method name 叫做 test 是不夠的,還要知道是沒有參數的那一個 test,或是接受一個 int 參數的 test,或是接受一個 String 參數的 test。很可惜 StackTraceElement 物件並沒有提供 method 參數這個資訊。

聰明的鄉民們可能會說,阿都可以知道該例外是在程式第幾行被丟出來的(可以從 StackTraceElement 物件的 getLineNumber() 得知),怎麼會沒辦法知道那一行是屬於哪一個 method?是啊,為什麼沒辦法?『某人』和 Teddy 就算把學弟打死,還是沒辦法。如果有 source code,當然可以知道 exception 是從哪一個 method 丟出來的,問題是,請複習一下需求:

在 runtime 當 Java 程式收到(catch)一個 exception 的時候,要如何得知該 exception 是從那一個 method 中所丟出來的。

請注意,是 runtime,正常情況是拿不到 source code 的,只有 bytecode。原本 Teddy 也以為是學弟偷懶不認真研究 Java Reflection 所以才找不到答案,後來 Teddy 自己下海找了 2 小時,發現好像還真的是無解。


***

講到這邊,一般正常人都會有個疑問:這和『第 N 度空間』有何關聯?是滴,Teddy 把 Java 語言提供的否些『功能』看作一種『空間』,這樣就扯上關係了。先從 Java  最基本的控制流程開始,例如 assignment,statement,loop,condition 這一些在傳統 procedure oriented languages (程序導向語言,例如 C)都有的功能,看成一種『空間』,有些問題在此空間就可以解決了,例如,用 Java 寫出 1 加到 100 的程式,根本不需使用其他『空間』(例如物件導向)來解這個問題。事實上,Teddy 看過很多『把 Java 當成 C 來用的程式』,也是跑得好好地。

問題是,當軟體系統大到一定的程度,人們發現光是用『程序導向空間』來解問題不足,因此有人就加入了『物件導向空間』來幫忙處理一些在『程序導向空間』不容易處理的問題。什麼問題?請自行複習一下 Class, Object, Interface, Inheritance, Polymorphism 這些機制。再過一陣子,又有一些前輩們發現,有些問題還是不容易解決,於是又產生了『Reflection  空間』。如果沒有 reflection,programmers 就不能用Class.forName 的方式在 runtime 的時候依據某個字串(某個 class 的 name)動態載入該個 class。當然 reflection 還有其他更多的用途,有興趣者請自行研究。

講到這邊,已經提到了 Java 三個空間了。還有沒有?當然有。Annotation 也可以算是另一度空間,有了 annotation ,programmers 就可以用達到某種程度的 『宣告式程式設計』。相信以上所提的 Java 四度空間一般的 Java programmers 都了解,很遺憾,在這四度空間中 Teddy 『一時』(要強調一下,是一時,說不定 Teddy 的問題可以在這四度空間中被解,只是 Teddy 還沒發現)沒能發現解法,還好 Teddy 有『通靈』的能力,當年在研究『如何在 Java 語言中實做 Design by Contract』這個題目的時候,花了好幾個月的時間在和 Java bytecode 搏鬥,因此知道這個問題在第五度空間『Java Bytecode』應該有解

請看一下 Teddy 用看 bytecode 軟體所開啟某個 Java class 的畫面。


在 bytecode 中,每一個 method 可以夾帶一種 LineNumberTable 的資料結構,在這個資料結構中會紀錄該 method 丟出 exception 的行號 (圖右方的 line_number 這個欄位所紀錄的值)。看到這邊答案就很清楚了,從 exception 物件可以得到 StackTraceElement 物件,然後再從 StackTraceElement 物件可以得到 exception 丟出點的行號。把這個行號拿來和 bytecode 裡面的 LineNumberTable 比對,如果數值一樣就找到丟出該 exception 的那一個 method  了。正所謂『江湖一點訣,說破不值錢』(怎麼覺的和變魔術那麼像?!),可以準備講完收工了。


鄉民甲:等一下,那要怎麼去讀 bytecode 裡面的資料?

Teddy:很簡單,網路上找一下就有答案了,有現成的 libraries 可以用。



***

解了這個問題之後,Teddy 才深深的感受到『有些問題在某些空間中比較容易被解』這件事。身為一個 Java programmers,如果對於上述的 Java 五個空間不了解,遇到問題時相對的可以使用的『工具』就比較少。至於在 Java 或是其他類似的語言到底有『幾度空間』其實 Teddy 也沒想過這個問題,不過肯定是『超過五度空間』。你問 Teddy 為什麼?啊因為隨便想都還可以想出好幾個啊...


***

友藏內心獨白:這種東西也敢寫出來...XD

4 則留言:

  1. 不大懂你要什麼,就算簡單的用jdk1.1.x的api應該就能做到,答案就在new Throwable().printStackTrace().
    簡單的寫,
    java.io.StringWriter sw = new java.io.StringWriter();
    java.io.PrintWriter pw = new java.io.PrintWriter(sw);
    new Throwable().printStackTrace(pw);
    String log = sw.toString();
    別忘了compiler的時候要可以輸出行號(預設有,為了讓檔案小點很多時候會關掉)。

    回覆刪除
  2. 你已經有了exception物件可以直接使用,不需要new 剛的作法是在任何一行中得知整串stack tree的作法。

    回覆刪除
  3. Hi M. Jow:

    Teddy 最終需要的不是例外產生的行號, 而是丟出例外的那一個 method 的 signature (method name + a list of parameter types). 您提供的程式範例應該是達不到 Teddy 的需求.

    回覆刪除
  4. 要知道a list of parameter types應該是沒,但是知道行號搭配版本就知道是哪一個丟出了。

    回覆刪除