通常,在運行 Java 程序時,必須使用 String[] 輸入參數將參數傳入,從 main() 方法運行程序。但是如果程序仍在調試中,那麼,這可能會成為一項繁重的任務。本月,Eric Allen 討論對程序中的表達式和語句進行交互式評價的優點,並且提供幾個幫助您處理這一任務的 Java repl(“read-eval-print-loop”工具,“讀取-評價-打印-循環”工具)。他還討論交互式評價如何幫助構建 GUI 和探索新的 API。
多數程序都包含很多方法,這些方法分布在為數眾多的類中。毫無疑問,從程序的 main 入口點測試所有這些方法,即使不是不可能的,也是很困難的。
這就是單元測試之所以有用的原因。許多程序員和軟件設計人員(包括我自己)強調單元測試在編寫健壯的的軟件時是有用的。但是如果您想能夠以一種交互性更好的方式訪問程序中的各種元素時,則可能要折衷一下。
當確實要這樣做時,為每個結果編寫、編譯並運行新的單元測試就會很快變成一件繁重的事情。我發現,當不能預知給予特定輸入後程序將表現出什麼行為時(例如:在 AI 程序中可能就會出現這種情況),尤其如此。
那麼,該怎麼辦呢?
不要為小改動著急
為了作一個類比,請考慮用通常情況下進行編譯的語言(例如:Java 或 C++)編程和用在更多情況下進行解釋的語言(Python 或 Scheme)編程之間的差異。
在編譯型語言中,每個編寫/測試/調試循環都必須包含編譯這一額外的步驟,這可能是一個單調乏味的經歷,尤其是對於一些小改動而言。這可能會使我們得出結論說,解釋型語言更流暢,因而也更易於修改。(這種靈活性是有代價的:解釋型語言通常更少對代碼執行靜態檢查,例如類型檢查。)
正如有時候我們可能想對程序作個改動,但不必經歷重編譯的麻煩一樣,我們也可能想檢查程序中的一些元素,但不必例行公事般在套件中添加一個新的單元測試。當確實想這樣做時,擁有傳統上稱為“讀取-評價-打印-循環”(即 repl)的工具可能會有所幫助。
repl是一個基於文本的工具,它以表達式作為輸入,在特定程序的上下文中進行評端,然後顯示結果。接著,它等待獲得另一個表達式作為輸入,然後重復這些操作。這樣的工具源於類似 Lisp 的語言,但它們也能在更新的語言(例如:Python)中使用。
repl 在 Java 編程中的好處
這樣的工具並非僅僅在這些語言中是有用的。Java 程序員也可以從使用 repl 中獲得好處,不只是調試方面,在其它方面也可以。
構建 GUI
當組裝一個 GUI 時,有許多組件需要布置和連接。當構造 GUI 時,您肯定會碰到以下這些事情:
組件之間將以不可見的方式相互作用。
在運行 GUI 之前寫出其所有代碼是相當費時的。
一旦您看到了 GUI 的實際視覺效果,不可避免地,您將會想更改 GUI 的某些方面。
這個問題的一種常見的“解決方案”是使用圖形化的 GUI 構建工具,例如那些包含在 JBuilder、Forte 和其它 IDE 中的 GUI 構建工具。我個人不喜歡這種辦法 ― 您很難知道這個工具會給您生成什麼樣的 Java 代碼,您也不可能在修改所生成的代碼時不冒喪失與 GUI 構建工具的兼容性的風險(事實上,有些 IDE 強行禁止您修改機器生成代碼的任何部分)。
此外,許多這類 GUI 構建工具在生成 Java 代碼時都使用專用 GUI 庫,因而限制了 GUI 的兼容性。
我發現使用 repl 來構建 GUI 要容易得多。我完全可以交互地定義每一個 GUI 組件,然後依次顯示它。我能夠立刻修正任何不喜歡的東西。然後,我可以與之交互並將這些組件粘貼到程序中。
探索新 API
使用 Java 語言編程最大的優點之一是,有數量龐大的 API 可以使用,它們可以與一切事物 ― 從數據庫到 Web 服務到電視 ― 對接。不過需要花些時間學習 API 的語義。
通常,Javadocs 不會對 API 的行為的每個方面都作出明確說明。對付這種窘境的辦法是直接測試 API,使用 repl 可以使測試快得多 ― 只要輸入一個方法調用看看其結果就行了!
一個額外的好處是,使用 repl 測試 API 還強化了大多數程序員的主要行為 ― 我們傾向於在實踐中取得最好的學習效果。
Java 編程中可用的 repl
那麼,如果 repl 有這麼多優點,下一個問題顯然是 Java 語言可以使用哪些 repl?
Jython
Jython(以前的名稱是 JPython)是 Python 的 Java 編程語言實現(包含一個 repl)(經認證,是 100% 純 Java 的)。實際上,它把 Python 編譯成(有點復雜)Java 源代碼或直接編譯成字節碼。
本著 Python 的精神,人們嘗試了各種辦法以提供 Java 和 Jython 之間的無縫互操作性。Jython 讓您可以訪問所有的 Java 標准庫,就好像您是自然地使用 Java 語言進行編程一樣,您也可以訪問現有的 Java 類文件。所以您不僅可以將 repl 用來與標准庫一起工作,還可以與已經編譯成字節碼的您自己的 Java(或 Jython)類一起工作。
使用 Jython repl 時的一個重要注意事項是您在寫 Python 表達式,而不是 Java 表達式。其積極的一面是您得到了 Python 在句法方面簡明而又優美的優點。
例如,假如我想構造一個新的散列表,這個表將 a 映射到 1、 b 映射到 2, c 映射到 3。使用 Jython 我所要寫的只是:
>>> h = {'a':1, 'b':2, 'c':3}
解釋器在每一個新的輸入行前顯示 >>> 。
當探索新的 GUI 設計時,Jython 句法也有很多優點。舉個例子來說,可以將 GUI 元素的各個域指定為構造函數的關鍵字參數,就像這樣:
>>> from javax.swing import *
>>> f = JFrame(visible=1)
這個示例說明了 Jython 和 Java 語言的其它一些差異:
導入語句的句法有很大不同。
整型被用來取代了布爾型(1 是真,0 是假)。
這裡是另一個示例,示例中 Jython 代碼為您節省了一些輸入,給 GUI 元素添加了動作偵聽器(action listener)。通常,這樣的偵聽器通過使用命令模式(Command Pattern)被指定為匿名內部類的實例。在 Python(和其它許多“腳本”語言)中,這樣的命令可以通過使用交互式函數定義更簡潔地加以指定。例如,讓我們在上述的交互式會話的基礎上給 JButton 添加一個簡單的動作偵聽器:
>>> def listener(event):
... print 'thank you'
>>>
這是 Jython 中的函數定義的一個示例。為了讓我們知道它什麼時候需要一個語句以便繼續進行,解釋器在下一行打印省略號(代替脫字符)。這個函數只需要一個參數,並會將“thank you”打印到標准輸出。我們可以將它用作動作偵聽器,如下:
>>> panel = JPanel()
>>> panel.add(JButton('press me', actionPerformed=listener))
>>> f.getContentPane().add(panel)
>>> f.pack()
現在,我們將有一個顯示在屏幕上的窗口,這個窗口有一個標有“press me”的按鈕,按下這個按鈕後將打印“thank you”到標准輸出。想像一下,如果使用 Java 代碼的話,這將需要多少語句啊。
當然,缺點也是有的。例如:
您失去了靜態類型檢查(盡管按理說靜態檢查在 repl 中沒有多少價值)。
因為您輸入到 repl 中的表達式不是 Java 代碼,所以您無法在轉換它們之前將表達式從 repl 中復制和粘貼到您的程序中。
當與 Java 代碼一起使用 Jython 時,您得承擔在腦海中同時處理兩種語言的額外腦力負擔(盡管有些人認為這樣做很好玩)。
DynamicJava
Java 可以使用的另一個 repl 是 DynamicJava,一個真正基於 Java(呃,基本上是對的)的開放源代碼的工具,它有一些不同之處:
repl 語言允許您不必在聲明變量時指定變量的靜態類型。
您不必在語句末尾添加一個分號。解釋器也會返回(隨意地) null 作為語句的評價結果。(如果語句根本不返回值,情況會好得多。)
不限制您從 repl 內訪問對象的私有字段。
對於初級 Java 程序員,這些不同之處是很重要的,因為它們可能會使他們感到很迷惑。更有經驗的程序員可能會樂於見到其中一些寬松的約束。無論如何,DynamicJava 都是一個健壯的、非常有用的軟件產品(而且它是免費的,這是有幫助的)。
repl 總結
希望本部分已經指出並演示了一種主要工具,它能讓您交互地對 Java 程序中的表達式和語句進行評價,而不陷入重編譯的泥沼 ― 這種工具就是“讀取-評價-打印-循環”即 repl 工具。我們還演示了 repl 如何體現它在構建 GUI 中的重要性,或者當您只是想快速地檢查大量可以使用的 Java API 時,它又是如何體現它的重要性的。