了解本教程的主要內(nèi)容,以及如何從中獲得最大收獲。
如果現(xiàn)在有人要開始完全重寫 Java,那么 Groovy 就像是 Java 2.0。Groovy 并沒有取代 Java,而是作為 Java 的補(bǔ)充,它提供了更簡單、更靈活的語法,可以在運(yùn)行時(shí)動態(tài)地進(jìn)行類型檢查。您可以使用 Groovy 隨意編寫 Java 應(yīng)用程序,連接 Java 模塊,甚至擴(kuò)展現(xiàn)有的 Java 應(yīng)用程序 — 甚至可以用 Groovy 對 Java 代碼進(jìn)行單元測試。Groovy 的美妙之處還在于,它能夠比編寫純粹的 Java 代碼更快地完成所有工作 — 有時(shí)候會快許多。
在本教程中,您將了解到 Groovy 是一門動態(tài)語言,它能夠像 Java 語言本身一樣很好地應(yīng)用于 Java 平臺。
本教程將逐步向您介紹 Groovy 的基本概念。您將學(xué)習(xí) Groovy 集合、Groovy 類,當(dāng)然還有 Groovy 的語法。完成本教程之后,您將了解將 Java 和 Groovy 結(jié)合使用的好處,從此您將能夠在日常的 Java 開發(fā)中使用 Groovy。
為了從本教程得到最大收獲,您應(yīng)該熟悉 Java 語法和在 Java 平臺上進(jìn)行面向?qū)ο箝_發(fā)的基本概念。
要嘗試本教程的代碼,需要安裝以下環(huán)境之一:
另外,本章教程假設(shè)您正在使用 Eclipse IDE。不需要安裝 Groovy,因?yàn)楸窘坛虝榻B如何安裝 Groovy Eclipse 插件。
本教程推薦系統(tǒng)的配置如下:
本教程的操作說明和示例均基于 Microsoft Windows 操作系統(tǒng)。本教程涉及的所有工具在 Linux 和 Unix 系統(tǒng)上也能工作。
這一節(jié)將學(xué)習(xí) Groovy 的基礎(chǔ)知識:它是什么,它與 Java 語言和 JVM 的關(guān)系,以及編寫 Groovy 代碼的一些要點(diǎn)。
Groovy 是 JVM 的一個(gè)替代語言 —替代 是指可以用 Groovy 在 Java 平臺上進(jìn)行 Java 編程,使用方式基本與使用 Java 代碼的方式相同。在編寫新應(yīng)用程序時(shí),Groovy 代碼能夠與 Java 代碼很好地結(jié)合,也能用于擴(kuò)展現(xiàn)有代碼。目前的 Groovy 版本是 1.5.4,在 Java 1.4 和 Java 5 平臺上都能使用,也能在 Java 6 上使用。
Groovy 的一個(gè)好處是,它的語法與 Java 語言的語法很相似。雖然 Groovy 的語法源于 Smalltalk 和 Ruby 這類語言的理念,但是可以將它想像成 Java 語言的一種更加簡單、表達(dá)能力更強(qiáng)的變體。(在這點(diǎn)上,Ruby 與 Groovy 不同,因?yàn)樗恼Z法與 Java 語法差異很大。)
許多 Java 開發(fā)人員非常喜歡 Groovy 代碼和 Java 代碼的相似性。從學(xué)習(xí)的角度看,如果知道如何編寫 Java 代碼,那就已經(jīng)了解 Groovy 了。Groovy 和 Java 語言的主要區(qū)別是:完成同樣的任務(wù)所需的 Groovy 代碼比 Java 代碼更少。(有時(shí)候會少很多?。?/p>
開始使用 Groovy 時(shí),您會發(fā)現(xiàn)它使日常的編程活動變得快了許多。完成本教程之后,您會了解更多的 Groovy 語法快捷方式。不過現(xiàn)在只需知道以下這些要點(diǎn):
public
。class
對象。 雖然 Groovy 允許省略 Java 語法中的一些元素,但也增加了一些新特性,例如本地集合、內(nèi)置的正則表達(dá)式和閉包。在標(biāo)準(zhǔn)的 Java 代碼中,如果想要?jiǎng)?chuàng)建一個(gè)項(xiàng)列表,首先要導(dǎo)入 java.util.ArrayList
,然后程序化地初始化 ArrayList
實(shí)例,然后 再向?qū)嵗刑砑禹?xiàng)。在 Groovy 中,列表和映射都內(nèi)置在語法中 — 無需導(dǎo)入任何內(nèi)容。正則表達(dá)式也不需要額外的導(dǎo)入或?qū)ο?;它們可以通過特殊的 Groovy 語法來創(chuàng)建。
對于任何 Java 開發(fā)人員來說,閉包都是一個(gè)令人興奮的新技巧。這些神奇的構(gòu)造將會包含在未來的 Java 發(fā)行版(很可能是 Java 7)中,成為正式的 Java 語法,但現(xiàn)在已經(jīng)可以在 Groovy 中使用了??梢詫?em>閉包 想像為一個(gè)代碼塊,可以現(xiàn)在定義,以后再執(zhí)行??梢允褂眠@些強(qiáng)大的構(gòu)造做許多漂亮的事,不過最著名的是簡化迭代。使用 Groovy 之后,就有可能再也不需要編寫 Iterator
實(shí)例了。
從技術(shù)上講,Groovy 可能是您最近聽說過的類型最松散的動態(tài)語言之一。從這個(gè)角度講,Groovy 與 Java 語言的區(qū)別很大,Java 語言是一種固定類型語言。在 Groovy 中,類型是可選的,所以您不必輸入 String myStr = "Hello";
來聲明 String
變量。
除此之外,Groovy 代碼還能在運(yùn)行時(shí)輕松地改變自己。這實(shí)際上意味著,能夠在運(yùn)行時(shí)輕松地為對象指定新方法和屬性。這一編程領(lǐng)域稱為元編程,Groovy 能夠很好地支持這種編程方式。在學(xué)習(xí)本教程的過程中,您將了解到關(guān)于 Groovy 的動態(tài)性質(zhì)的更多內(nèi)容?,F(xiàn)在惟一要補(bǔ)充的是,您會驚訝地發(fā)現(xiàn),在 Groovy 會使操作 XML 或普通的 java.io.File
實(shí)例變得非常輕松。
用 Groovy 編寫的任何內(nèi)容都可以編譯成標(biāo)準(zhǔn)的 Java 類文件并在 Java 代碼中重用。類似地,用標(biāo)準(zhǔn) Java 代碼編寫的內(nèi)容也可以在 Groovy 中重用。所以,可以輕易地使用 Groovy 為 Java 代碼編寫單元測試。而且,如果用 Groovy 編寫一個(gè)方便的小工具,那么也可以在 Java 程序中使用這個(gè)小工具。
學(xué)習(xí)新語言并不是件小事,即使是 Groovy 也不例外。這一節(jié)將介紹學(xué)習(xí) Groovy 的更多動力。另外還將第一次看到一些 Groovy 代碼,并了解 Groovy 與 Java 編程的比較。
即使 Groovy 與 Java 語言有許多相似之處,它仍然是另一個(gè)語言。您可能想知道為什么應(yīng)該花時(shí)間學(xué)習(xí)它。簡單的回答就是:Groovy 是一種更有生產(chǎn)力 的語言。它具有松散的語法和一些特殊功能,能夠加快編碼速度。
只用一個(gè)示例即可說明問題:一旦發(fā)現(xiàn)使用 Groovy 在集合中導(dǎo)航的容易程度,您就再也不會用 Java 處理集合導(dǎo)航了。能夠用 Groovy 快速編寫代碼,這還意味著能夠更快地收到反饋,更不用說完成任務(wù)列表中的工作帶來的滿足感了。在較高層面上,如果能更快地將代碼交付給利益相關(guān)者,那么就能在更短的時(shí)間內(nèi)交給他們更多發(fā)行版。實(shí)際上,Groovy 比 Java 更有助于敏捷開發(fā)。
如果仍然覺得采用新語言很困難,那么可以看看將 Groovy 集成到開發(fā)環(huán)境有多么容易。您無需安裝新的運(yùn)行時(shí)工具或?qū)iT的 IDE。實(shí)際上,只需將 Groovy 的一個(gè) jar 文件放在類路徑中即可。
而且,Groovy 是一種開源語言,由熱心的 Java 開發(fā)人員社區(qū)管理。因?yàn)?Groovy 獲得 Apache Software License, Version 2.0,所以可以自由地使用它開發(fā)自由軟件和私有軟件。
買車的時(shí)候,如果不試駕一下,是不會買的。所以,在要求您安裝 Groovy 之前,我會演示一些代碼。首先,回顧一下用 Java 如何創(chuàng)建、編譯和運(yùn)行標(biāo)準(zhǔn)的 Hello World 示例;然后再看看如何使用 Groovy 代碼執(zhí)行同一過程。比較這兩個(gè)示例,很容易就能看到這兩種語言之間的差異。
用 Java 編寫的典型的 Hello World 示例如下所示:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); }}
在這個(gè)簡單的 HelloWorld
類中,我省略了包,而且向控制臺輸出的時(shí)候沒有使用任何多余的編碼約定。下一步是用 javac
編譯這個(gè)類,如下所示:
c:>javac HelloWorld.java
最后,運(yùn)行經(jīng)過編譯的類:
c:>java HelloWorld
迄今為止還不錯(cuò) — 很久以前就會編這么基礎(chǔ)的代碼了,所以這里只是回顧一下。下面,請看用 Groovy 編碼的相同過程。
就像前面提到過的,Groovy 支持松散的 Java 語法 — 例如,不需要為打印 “Hello World!” 這樣的簡單操作定義類。
而且,Groovy 使日常的編碼活動變得更容易,例如,Groovy 允許輸入 println
,而無需輸入 System.out.println
。當(dāng)您輸入 println
時(shí),Groovy 會非常聰明地知道您指的是 System.out
。
所以,用 Groovy 編寫 Hello World 程序就如下面這樣簡單:
println "Hello World!"
請注意,在這段代碼周圍沒有類結(jié)構(gòu),而且也沒有方法結(jié)構(gòu)!我還使用 println
代替了 System.out.println
。
假設(shè)我將代碼保存在文件 MyFirstExample.groovy 內(nèi),只要輸入以下代碼就能運(yùn)行這個(gè)示例:
c:>groovy MyFirstExample.groovy
在控制臺上輸出 “Hello World!” 所需的工作就這么多。
您可能注意到了,我不必編譯 .groovy
文件。這是因?yàn)?Groovy 屬于腳本語言。腳本語言的一個(gè)特點(diǎn)就是能夠在運(yùn)行時(shí)進(jìn)行解釋。(在 Java 中,要從源代碼編譯生成字節(jié)碼,然后才能進(jìn)行解釋。區(qū)別在于腳本語言能夠直接 解釋源代碼。)
Groovy 允許完全省略編譯步驟,不過仍然可以 進(jìn)行編譯。如果想要編譯代碼,可以使用 Groovy 編譯器 groovyc
。用 groovyc
編譯 Groovy 代碼會產(chǎn)生標(biāo)準(zhǔn)的 Java 字節(jié)碼,然后可以通過 java
命令運(yùn)行生成的字節(jié)碼。這是 Groovy 的一項(xiàng)經(jīng)常被忽略的關(guān)鍵特性:用 Groovy 編寫的所有代碼都能夠通過標(biāo)準(zhǔn) Java 運(yùn)行時(shí)編譯和運(yùn)行。
至于運(yùn)行代碼,如果我希望更加簡潔,我甚至還能輸入
c:>groovy -e "println 'Hello World!'"
這會生成相同的結(jié)果,而且甚至無需定義任何文件!
在這一節(jié)中,將真正開始進(jìn)行 Groovy 編程。首先,學(xué)習(xí)如何輕松地安裝 Groovy(通過 Eclipse Groovy 插件),然后從一些有助于了解 Groovy 的簡單示例開始。
為了迅速開始使用 Groovy,需要做的全部工作就是安裝 Eclipse 的 Groovy 插件。打開 Ecliplse,在 Help 菜單中選擇 Software Updates > Find and Install...。
圖 1 顯示了執(zhí)行以上步驟之后出現(xiàn)的對話框:
接下來,出現(xiàn)一個(gè)對話框,里面包含兩個(gè)選項(xiàng)。請選擇 Search for new features to install 單選按鈕。單擊 Next 按鈕,然后選擇 New Remote Site...。出現(xiàn)一個(gè)新的對話框,里面包含兩個(gè)需要填寫的字段:新位置的名稱和該位置的 URL,如圖 2 所示:
輸入 “Groovy plugin
” 作為名稱,輸入 “http://dist.codehaus.org/groovy/distributions/update/
” 作為位置,單擊 OK 按鈕,在隨后出現(xiàn)的 Sites to include in search 框中確保選中了名為 “Groovy plugin” 的項(xiàng)目 — 現(xiàn)在的列表應(yīng)該如圖 3 所示。
單擊 Finish 按鈕之后,應(yīng)該會出現(xiàn) Search Results 對話框。請?jiān)俅未_定選中了 “Groovy plugin” 框并單擊 Next 按鈕,這一步驟如圖 4 所示:
經(jīng)過一系列確認(rèn)之后,將會下載插件,然后可能需要重新啟動 Eclipse。
Eclipse 重啟之后,就能夠創(chuàng)建第一個(gè) Groovy 項(xiàng)目了。請確保創(chuàng)建兩個(gè)源文件夾 — 一個(gè)稱為 “groovy”,另一個(gè)稱為 “java”。編寫的 Groovy 代碼放在 groovy 文件夾,Java 代碼放在 java 文件夾。我發(fā)現(xiàn)將二者分開將會很有用,如圖 5 所示:
項(xiàng)目創(chuàng)建之后,右鍵單擊項(xiàng)目的圖標(biāo),應(yīng)該會看到一個(gè) Groovy 選項(xiàng),如圖 6 所示。請選擇該選項(xiàng),然后選擇 Add Groovy Nature 選項(xiàng)。這樣做可以將必要的 Groovy 庫、編譯器和運(yùn)行程序?qū)氲巾?xiàng)目中。
創(chuàng)建 Groovy 類很簡單。選擇 groovy
文件夾并右鍵單擊它。選擇 New,然后選擇 Other,如圖 7 所示:
在這里,找到 Groovy 文件夾,并選擇 Groovy Class— 應(yīng)該會看到一個(gè)對話框,如圖 8 所示。
單擊 Next 按鈕,系統(tǒng)將要求您提供類的名稱。輸入 HelloWorld
。
現(xiàn)在可以將 HelloWorld
Groovy 類保留在默認(rèn)包內(nèi),如圖 9 所示。
雖然步驟看起來很多,但這與創(chuàng)建標(biāo)準(zhǔn)的 Java 類并沒有什么區(qū)別。
單擊 Finish 按鈕,應(yīng)該會看到如下所示的代碼段:
class HelloWorld { static void main(args) { }}
這看起來同前面的 Java HelloWorld
示例驚人地相似。但是請注意,它不包含 public
修改符。而且,如果仔細(xì)查看 main
方法的參數(shù),會注意到它沒有類型。
現(xiàn)在在 main
方法內(nèi)加入 println "Hello World"
,完成后的代碼看起來如下所示:
class HelloWorld { static void main(args) { println "Hello World" }}
在源代碼編輯器中應(yīng)該能夠右鍵單擊,并選擇 Compile Groovy File 選項(xiàng),如圖 10 所示。
接下來,再次右鍵單擊文件,選擇 Run As 選項(xiàng),然后選擇 Groovy 選項(xiàng)。在 Eclipse 控制臺中應(yīng)該會看到輸出的 “Hello World”,如圖 11 所示。
OK,那么這是一種突出重點(diǎn)的取巧方式。Groovy 實(shí)際上就是 Java。其語法不同 — 多數(shù)情況下會短一些 — 但 Groovy 代碼 100% 符合 Java 字節(jié)碼標(biāo)準(zhǔn)。下一節(jié)將進(jìn)一步介紹這兩種語言的交叉。
前面已經(jīng)看到 Groovy 與 Java 代碼實(shí)際上可以互換的第一個(gè)證據(jù)。這一節(jié)將進(jìn)一步證明這點(diǎn),繼續(xù)使用 Groovy 構(gòu)建的 HelloWorld
類。
為了使您確信 Groovy 就是 Java,現(xiàn)在在 HelloWorld
類聲明和方法聲明前面加上 public
修改符,如下所示:
public class HelloWorld { public static void main(args) { println "Hello World" }}
這個(gè)代碼運(yùn)行起來同前面的代碼完全一樣。但是,如果仍不確信,還可以在 args
參數(shù)前加上 String[]
:
public class HelloWorld { public static void main(String[]args) { println "Hello World" }}
現(xiàn)在,還可以將 println
替換為 System.out.println
— 而且不要忘記加上括號。
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World") }}
現(xiàn)在的代碼與前面用 Java 編寫的 Hello World 示例完全相同,但是哪個(gè)示例更容易編寫呢?
請注意,原來的基于 Groovy 的 HelloWorld
類沒有任何 public
修改符,沒有任何類型(沒有 String[]
),而且提供了沒有括號的 println
快捷方式。
如果喜歡,可以將這個(gè)過程完全翻轉(zhuǎn)過來,回到基于 Java 的 Hello World 示例,刪除文件里的所有內(nèi)容,只保留 System.out
行,然后在這行刪除 System.out
和括號。最后只剩下:
println "Hello World"
現(xiàn)在,哪個(gè)程序更容易編寫呢?
Groovy 代碼完全符合 Java 字節(jié)碼標(biāo)準(zhǔn),這個(gè)練習(xí)證明了這一點(diǎn)。在 Eclipse 中,選擇 Run 菜單選項(xiàng) Open Run Dialog...。選擇一個(gè)新的 Java Application 配置。確保項(xiàng)目是您的 Groovy 項(xiàng)目。對于 Main
類,單擊 Search 按鈕,找到 HelloWorld
類。請注意,單詞 class 表明 Eclipse Groovy 插件已經(jīng)將 .groovy 文件編譯為 .class 文件。
在圖 12 中可以看到整個(gè)這個(gè)過程 — 如果以前在 Eclipse 中運(yùn)行過 Java 類,那么您應(yīng)該對這個(gè)過程很熟悉。
單擊 Run 按鈕,看到什么了?實(shí)際上,“Hello World!” 從未像現(xiàn)在這樣能夠說明問題。
很可能將 Groovy 當(dāng)成是沒有規(guī)則的 Java 代碼。但實(shí)際上,Groovy 只是規(guī)則少一些。這一節(jié)的重點(diǎn)是使用 Groovy 編寫 Java 應(yīng)用程序時(shí)可以不用考慮的一個(gè) Java 編程的具體方面:類型定義。
在 Java 中,如果要聲明一個(gè) String
變量,則必須輸入:
String value = "Hello World";
但是,如果仔細(xì)想想,就會看出,等號右側(cè)的字符已經(jīng)表明 value
的類型是 String
。所以,Groovy 允許省略 value
前面的 String
類型變量,并用 def
代替。
def value = "Hello World"
實(shí)際上,Groovy 會根據(jù)對象的值來判斷它的類型。
將 HelloWorld.groovy 文件中的代碼編輯成下面這樣:
String message = "Hello World"println message
運(yùn)行這段代碼,應(yīng)該會在控制臺上看到與前面一樣的 “Hello World”?,F(xiàn)在,將變量類型 String
替換為 def
并重新運(yùn)行代碼。是不是注意到了相同的結(jié)果?
除了輸出 message
的值,還可以用以下調(diào)用輸出它的類型:
def message = "Hello World"println message.class
輸出 “class java.lang.String
” 應(yīng)該是目前為止很受歡迎的一項(xiàng)變化!如果想知道到底發(fā)生了什么,那么可以告訴您:Groovy 推斷出 message
一定是 String
類型的,因?yàn)樗闹凳怯秒p引號括起來的。
您可能聽說過,在 Groovy 中 “一切都是對象” — 但對于類型來說這句話意味著什么呢?讓我們看看如果將前面示例中的 String
替換為數(shù)字會怎么樣,如下所示:
def message = 12println message.class
message
變量的數(shù)字值看起來像是 Java 的原生類型 int
。但是,運(yùn)行這個(gè)代碼就可以看出,Groovy 將它作為 Integer
。這是因?yàn)樵?Groovy 中 “一切都是對象”。
Java 中的所有對象都擴(kuò)展自 java.lang.Object
,這對 Groovy 來說非常方便。即使在最糟的情況下,Groovy 運(yùn)行時(shí)不能確定變量的類型,它只需將變量當(dāng)成 Object
,問題就解決了。
繼續(xù)使用這段代碼。將 message
改成自己喜歡的任意類型:Groovy 會在運(yùn)行時(shí)盡其所能推斷出這個(gè)變量的類型。
那么,Groovy 缺少類型意味著所需的輸入更少。不可否認(rèn),將 String
替換成 def
并沒有真正節(jié)約多少打字工作 — 三個(gè)字母并不值得如何夸耀!但是在更高的層次上看,在編寫大量不僅僅包含變量聲明的代碼的時(shí)候,沒有類型確實(shí)減少了許多打字工作。更重要的是,這意味著要閱讀的代碼要少得多。最后,Groovy 缺少類型能夠帶來更高的靈活性 — 不需要接口或抽象類。
所以,只需要使用 def
關(guān)鍵字就能在方法中聲明一個(gè)獨(dú)立變量,不需要將 def
關(guān)鍵字作為方法聲明中的參數(shù)。在 for
循環(huán)聲明中也不需要它,這意味著不用編寫 (int x = 0; x < 5; x++)
,相反,可以省略 int
,保留空白。
同大多數(shù)腳本語言一樣,Groovy 經(jīng)常被宣傳為生產(chǎn)力更高 的 Java 語言替代品。您已經(jīng)看到了 Groovy 缺少類型能夠如何減少打字工作。在這一節(jié),將創(chuàng)建并試用一個(gè) repeat
函數(shù)。在這個(gè)過程中,將進(jìn)一步探索 Groovy 提高效率的方式。
下面這種方法可以更好地感受 Groovy 缺乏類型的好處:首先,用與創(chuàng)建 HelloWorld
相同的方式創(chuàng)建一個(gè) Groovy 類,將這個(gè)類稱為 MethodMadness
,并刪除自動生成的類體:將要定義一個(gè)獨(dú)立的 repeat
函數(shù)?,F(xiàn)在在控制臺中輸入以下代碼:
def repeat(val){ for(i = 0; i < 5; i++){ println val }}
起初,從 Java 的角度來看,這個(gè)小函數(shù)看起來可能有些怪(實(shí)際上,它很像 JavaScript)。但它就是 Java 代碼,只不過是用 Groovy 的樣式編寫的。
repeat
函數(shù)接受一個(gè)變量 val
。請注意參數(shù)不需要 def
。方法體本質(zhì)上就是一個(gè) for
循環(huán)。
調(diào)用這個(gè)函數(shù)。
repeat("hello world")
會輸出 “hello world” 五次。請注意,for
循環(huán)中省略了 int
。沒有變量類型的 for
循環(huán)要比標(biāo)準(zhǔn)的 Java 代碼短些?,F(xiàn)在看看如果在代碼里加入范圍會出現(xiàn)什么情況。
范圍 是一系列的值。例如 “0..4
” 表明包含 整數(shù) 0、1、2、3、4。Groovy 還支持排除范圍,“0..<4
” 表示 0、1、2、3。還可以創(chuàng)建字符范圍:“a..e
” 相當(dāng)于 a、b、c、d、e?!?code>a..<e” 包括小于 e 的所有值。
范圍為循環(huán)帶來了很大的方便。例如,前面從 0 遞增到 4 的 for
循環(huán)如下所示:
for(i = 0; i < 5; i++)
范圍可以將這個(gè) for
循環(huán)變得更簡潔,更易閱讀:
def repeat(val){ for(i in 0..5){ println val }}
如果運(yùn)行這個(gè)示例,可能會注意到一個(gè)小問題:“Hello World” 輸出了六次而不是五次。這個(gè)問題有三種解決方法:
for(i in 0..4)
def repeat(val){ for(i in 1..5){ println val }}
def repeat(val){ for(i in 0..<5){ println val }}
不論采用哪種方法,都會得到原來的效果 — 輸出 “Hello World” 五次。
現(xiàn)在已經(jīng)成功地使用 Groovy 的范圍表達(dá)式縮短了 repeat
函數(shù)。但這個(gè)函數(shù)依然有些限制。如果想重復(fù) “Hello World” 八次該怎么辦?如果想對不同的值重復(fù)不同次數(shù) — 比如 “Hello World” 重復(fù)八次,“Goodbye Sunshine” 重復(fù)兩次,這時(shí)該怎么辦?
每次調(diào)用 repeat
時(shí)都要指定需要的重復(fù)次數(shù)的做法已經(jīng)過時(shí)了,特別是在已經(jīng)適應(yīng)了默認(rèn)行為(重復(fù)五次)的時(shí)候。
Groovy 支持默認(rèn)參數(shù)值,可以在函數(shù)或方法的正式定義中指定參數(shù)的默認(rèn)值。調(diào)用函數(shù)的程序可以選擇省略參數(shù),使用默認(rèn)值。
使用前面的 repeat
函數(shù)時(shí),如果希望調(diào)用程序能夠指定重復(fù)值,可以像下面這樣編碼:
def repeat(val, repeat=5){ for(i in 0..<repeat){ println val }}
像下面這樣調(diào)用該函數(shù):
repeat("Hello World", 2)repeat("Goodbye sunshine", 4)repeat("foo")
結(jié)果會輸出 “Hello World” 兩次,“Goodbye sunshine” 四次,“foo” 五次(默認(rèn)次數(shù))。
在 Groovy 提供的所有方便的快捷方式和功能中,最有幫助的一個(gè)可能就是內(nèi)置的 集合?;叵胍幌略?Java 編程中是如何使用集合的 — 導(dǎo)入 java.util
類,初始化集合,將項(xiàng)加入集合。這三個(gè)步驟都會增加不少代碼。
而 Groovy 可以直接在語言內(nèi)使用集合。在 Groovy 中,不需要導(dǎo)入專門的類,也不需要初始化對象。集合是語言本身的本地成員。Groovy 也使集合(或者列表)的操作變得非常容易,為增加和刪除項(xiàng)提供了直觀的幫助。
在前一節(jié)學(xué)習(xí)了如何用 Groovy 的范圍將循環(huán)變得更容易。范圍表達(dá)式 “0..4
” 代表數(shù)字的集合— 0、1、2、3 和 4。為了驗(yàn)證這一點(diǎn),請創(chuàng)建一個(gè)新類,將其命名為 Ranger
。保留類定義和 main
方法定義。但是這次添加以下代碼:
def range = 0..4println range.classassert range instanceof List
請注意,assert
命令用來證明范圍是 java.util.List
的實(shí)例。接著運(yùn)行這個(gè)代碼,證實(shí)該范圍現(xiàn)在是類型 List
的集合。
Groovy 的集合支持相當(dāng)豐富,而且美妙之處就在于,在 Groovy 的魔法背后,一切都是標(biāo)準(zhǔn)的 Java 對象。每個(gè) Groovy 集合都是 java.util.Collection
或 java.util.Map
的實(shí)例。
前面提到過,Groovy 的語法提供了本地列表和映射。例如,請將以下兩行代碼添加到 Ranger
類中:
def coll = ["Groovy", "Java", "Ruby"]assert coll instanceof Collectionassert coll instanceof ArrayList
你將會注意到,coll
對象看起來很像 Java 語言中的數(shù)組。實(shí)際上,它是一個(gè) Collection
。要在普通的 Java 代碼中得到集合的相同實(shí)例,必須執(zhí)行以下操作:
Collection<String> coll = new ArrayList<String>();coll.add("Groovy");coll.add("Java");coll.add("Ruby");
在 Java 代碼中,必須使用 add()
方法向 ArrayList
實(shí)例添加項(xiàng)。
Groovy 提供了許多方法可以將項(xiàng)添加到列表 — 可以使用 add()
方法(因?yàn)榈讓拥募鲜且粋€(gè)普通的 ArrayList
類型),但是還有許多快捷方式可以使用。
例如,下面的每一行代碼都會向底層集合加入一些項(xiàng):
coll.add("Python")coll << "Smalltalk"coll[5] = "Perl"
請注意,Groovy 支持操作符重載 —<<
操作符被重載,以支持向集合添加項(xiàng)。還可以通過位置參數(shù)直接添加項(xiàng)。在這個(gè)示例中,由于集合中只有四個(gè)項(xiàng),所以 [5]
操作符將 “Perl” 放在最后。請自行輸出這個(gè)集合并查看效果。
如果需要從集合中得到某個(gè)特定項(xiàng),可以通過像上面那樣的位置參數(shù)獲取項(xiàng)。例如,如果想得到第二個(gè)項(xiàng) “Java”,可以編寫下面這樣的代碼(請記住集合和數(shù)組都是從 0 開始):
assert coll[1] == "Java"
Groovy 還允許在集合中增加或去掉集合,如下所示:
def numbers = [1,2,3,4]assert numbers + 5 == [1,2,3,4,5]assert numbers - [2,3] == [1,4]
請注意,在上面的代碼中, 實(shí)際上創(chuàng)建了新的 集合實(shí)例,由最后一行可以看出。
Groovy 還為集合添加了其他一些方便的功能。例如,可以在集合實(shí)例上調(diào)用特殊的方法,如下所示:
def numbers = [1,2,3,4]assert numbers.join(",") == "1,2,3,4" assert [1,2,3,4,3].count(3) == 2
join()
和 count()
只是在任何項(xiàng)列表上都可以調(diào)用的眾多方便方法中的兩個(gè)。分布操作符(spread operator) 是個(gè)特別方便的工具,使用這個(gè)工具不用在集合上迭代,就能夠調(diào)用集合的每個(gè)項(xiàng)上的方法。
假設(shè)有一個(gè) String
列表,現(xiàn)在想將列表中的項(xiàng)目全部變成大寫,可以編寫以下代碼:
assert ["JAVA", "GROOVY"] == ["Java", "Groovy"]*.toUpperCase()
請注意 *.
標(biāo)記。對于以上列表中的每個(gè)值,都會調(diào)用 toUpperCase()
,生成的集合中每個(gè) String
實(shí)例都是大寫的。
除了豐富的列表處理功能,Groovy 還提供了堅(jiān)固的映射機(jī)制。同列表一樣,映射也是本地?cái)?shù)據(jù)結(jié)構(gòu)。而且 Groovy 中的任何映射機(jī)制在幕后都是 java.util.Map
的實(shí)例。
Java 語言中的映射是名稱-值對的集合。所以,要用 Java 代碼創(chuàng)建典型的映射,必須像下面這樣操作:
Map<String, String>map = new HashMap<String, String>();map.put("name", "Andy");map.put("VPN-#","45");
一個(gè) HashMap
實(shí)例容納兩個(gè)名稱-值對,每一個(gè)都是 String
的實(shí)例。
Groovy 使得處理映射的操作像處理列表一樣簡單 — 例如,可以用 Groovy 將上面的 Java 映射寫成
def hash = [name:"Andy", "VPN-#":45]
請注意,Groovy 映射中的鍵不必是 String
。在這個(gè)示例中,name
看起來像一個(gè)變量,但是在幕后,Groovy 會將它變成 String
。
接下來創(chuàng)建一個(gè)新類 Mapper
并加入上面的代碼。然后添加以下代碼,以證實(shí)底層的代碼是真正的 Java 代碼:
assert hash.getClass() == java.util.LinkedHashMap
可以看到 Groovy 使用了 Java 的 LinkedHashMap
類型,這意味著可以使用標(biāo)準(zhǔn)的 Java 一樣語句對 hash
中的項(xiàng)執(zhí)行 put
和 get
操作。
hash.put("id", 23)assert hash.get("name") == "Andy"
現(xiàn)在您已經(jīng)看到,Groovy 給任何語句都施加了魔法,所以可以用 .
符號將項(xiàng)放入映射中。如果想將新的名稱-值對加入映射(例如 dob
和 “01/29/76”),可以像下面這樣操作:
hash.dob = "01/29/76"
.
符號還可以用來獲取項(xiàng)。例如,使用以下方法可以獲取 dob
的值:
assert hash.dob == "01/29/76"
當(dāng)然 .
要比調(diào)用 get()
方法更具 Groovy 特色。
還可以使用假的位置語法將項(xiàng)放入映射,或者從映射獲取項(xiàng)目,如下所示:
assert hash["name"] == "Andy"hash["gender"] = "male"assert hash.gender == "male"assert hash["gender"] == "male"
但是,請注意,在使用 []
語法從映射獲取項(xiàng)時(shí),必須將項(xiàng)作為 String
引用。
現(xiàn)在,閉包是 Java 世界的一個(gè)重大主題,對于是否會在 Java 7 中包含閉包仍然存在熱烈的爭論。有些人會問:既然 Groovy 中已經(jīng)存在閉包,為什么 Java 語言中還需要閉包?這一節(jié)將學(xué)習(xí) Groovy 中的閉包。如果沒有意外,在閉包成為 Java 語法的正式部分之后,這里學(xué)到的內(nèi)容將給您帶來方便。
雖然在前幾節(jié)編寫了不少集合代碼,但還沒有實(shí)際地在集合上迭代。當(dāng)然,您知道 Groovy 就是 Java,所以如果愿意,那么總是能夠得到 Java 的 Iterator
實(shí)例,用它在集合上迭代,就像下面這樣:
def acoll = ["Groovy", "Java", "Ruby"] for(Iterator iter = acoll.iterator(); iter.hasNext();){ println iter.next()}
實(shí)際上在 for
循環(huán)中并不需要類型聲明,因?yàn)?Groovy 已經(jīng)將迭代轉(zhuǎn)變?yōu)槿魏渭系闹苯映蓡T。在這個(gè)示例中,不必獲取 Iterator
實(shí)例并直接操縱它,可以直接在集合上迭代。而且,通常放在循環(huán)構(gòu)造內(nèi)的行為(例如 for
循環(huán)體中 println
)接下來要放在閉包內(nèi)。在深入之前,先看看如何執(zhí)行這步操作。
對于上面的代碼,可以用更簡潔的方式對集合進(jìn)行迭代,如下所示:
def acoll = ["Groovy", "Java", "Ruby"] acoll.each{ println it}
請注意,each
直接在 acoll
實(shí)例內(nèi)調(diào)用,而 acoll
實(shí)例的類型是 ArrayList
。在 each
調(diào)用之后,引入了一種新的語法 —{
,然后是一些代碼,然后是 }
。由 {}
包圍起來的代碼塊就是閉包。
閉包是可執(zhí)行的代碼塊。它們不需要名稱,可以在定義之后執(zhí)行。所以,在上面的示例中,包含輸出 it
(后面將簡單解釋 it
)的行為的無名閉包將會在 acoll
集合類型中的每個(gè)值上被調(diào)用。
在較高層面上,{}
中的代碼會執(zhí)行三次,從而生成如圖 13 所示的輸出。
閉包中的 it
變量是一個(gè)關(guān)鍵字,指向被調(diào)用的外部集合的每個(gè)值 — 它是默認(rèn)值,可以用傳遞給閉包的參數(shù)覆蓋它。下面的代碼執(zhí)行同樣的操作,但使用自己的項(xiàng)變量:
def acoll = ["Groovy", "Java", "Ruby"] acoll.each{ value -> println value}
在這個(gè)示例中,用 value
代替了 Groovy 的默認(rèn) it
。
閉包在 Groovy 中頻繁出現(xiàn),但是,通常用于在一系列值上迭代的時(shí)候。請記住,一系列值可以用多種方式表示,不僅可以用列表表示 — 例如,可以在映射、String
、JDBC Rowset
、File
的行上迭代,等等。
如果想在前面一節(jié) “Groovy 中的映射” 中的 hash
對象上迭代,可以編寫以下代碼:
def hash = [name:"Andy", "VPN-#":45]hash.each{ key, value -> println "${key} : ${value}"}
請注意,閉包還允許使用多個(gè)參數(shù) — 在這個(gè)示例中,上面的代碼包含兩個(gè)參數(shù)(key
和 value
)。
以下是使用典型的 Java 構(gòu)造如何進(jìn)行同樣的迭代:
Map<String, String>map = new HashMap<String, String>();map.put("name", "Andy");map.put("VPN-#","45"); for(Iterator iter = map.entrySet().iterator(); iter.hasNext();){ Map.Entry entry = (Map.Entry)iter.next(); System.out.println(entry.getKey() + " : " + entry.getValue());}
上面的代碼比 Groovy 的代碼長得多,是不是?如果要處理大量集合,那么顯然用 Groovy 處理會更方便。
請記住,凡是集合或一系列的內(nèi)容,都可以使用下面這樣的代碼進(jìn)行迭代。
"ITERATION".each{ println it.toLowerCase()}
雖然在迭代上使用閉包的機(jī)會最多,但閉包確實(shí)還有其他用途。因?yàn)殚]包是一個(gè)代碼塊,所以能夠作為參數(shù)進(jìn)行傳遞(Groovy 中的函數(shù)或方法不能這樣做)。閉包在調(diào)用的時(shí)候才會執(zhí)行這一事實(shí)(不是在定義的時(shí)候)使得它們在某些場合上特別有用。
例如,通過 Eclipse 創(chuàng)建一個(gè) ClosureExample
對象,并保持它提供的默認(rèn)類語法。在生成的 main()
方法中,添加以下代碼:
def excite = { word -> return "${word}!!"}
這段代碼是名為 excite
的閉包。這個(gè)閉包接受一個(gè)參數(shù)(名為 word
),返回的 String
是 word
變量加兩個(gè)感嘆號。請注意在 String
實(shí)例中替換 的用法。在 String
中使用 ${value}
語法將告訴 Groovy 替換 String
中的某個(gè)變量的值??梢詫⑦@個(gè)語法當(dāng)成 return word + "!!"
的快捷方式。
既然有了閉包,下面就該實(shí)際使用它了??梢酝ㄟ^兩種方法調(diào)用閉包:直接調(diào)用或者通過 call()
方法調(diào)用。
繼續(xù)使用 ClosureExample
類,在閉包定義下面添加以下兩行代碼:
assert "Groovy!!" == excite("Groovy")assert "Java!!" == excite.call("Java")
可以看到,兩種調(diào)用方式都能工作,但是直接調(diào)用的方法更簡潔。不要忘記閉包在 Groovy 中也是一類對象 — 既可以作為參數(shù)傳遞,也可以放在以后執(zhí)行。用普通的 Java 代碼可以復(fù)制同樣的行為,但是不太容易。現(xiàn)在不會感到驚訝了吧?
迄今為止,您已經(jīng)用 Groovy 輸出了許多次 “Hello World”,已經(jīng)操作了集合,用閉包在集合上迭代,也定義了您自己的閉包。做所有這些工作時(shí),甚至還沒有討論那個(gè)對 Java 開發(fā)人員來說至關(guān)重要的概念 — 類。
當(dāng)然,您已經(jīng)在這個(gè)教程中使用過類了:您編寫的最后幾個(gè)示例就是在不同類的 main()
方法中。而且,您已經(jīng)知道,在 Groovy 中可以像在 Java 代碼中一樣定義類。惟一的區(qū)別是,不需要使用 public
修改符,而且還可以省略方法參數(shù)的類型。這一節(jié)將介紹使用 Groovy 類能夠進(jìn)行的其他所有操作。
我們先從用 Groovy 定義一個(gè)簡單的 JavaBean 形式的類開始,這個(gè)類稱為Song
。
第一步自然是用 Groovy 創(chuàng)建名為 Song
的類。這次還要為它創(chuàng)建一個(gè)包結(jié)構(gòu) — 創(chuàng)建一個(gè)包名,例如 org.acme.groovy
。
創(chuàng)建這個(gè)類之后,刪除 Groovy 插件自動生成的 main()
。
歌曲有一些屬性 — 創(chuàng)作歌曲的藝術(shù)家、歌曲名稱、風(fēng)格等等。請將這些屬性加入新建的 Song
類,如下所示:
package org.acme.groovyclass Song { def name def artist def genre}
迄今為止還不錯(cuò),是不是?對于 Grooovy 的新開發(fā)人員來說,還不算太復(fù)雜!
應(yīng)該還記得本教程前面說過 Groovy 編譯器為用 Groovy 定義的每個(gè)類都生成標(biāo)準(zhǔn)的 Java .class
。還記得如何用 Groovy 創(chuàng)建 HelloWorld
類、找到 .class
文件并運(yùn)行它么?也可以用新定義的 Song
類完成同樣的操作。如果通過 Groovy 的 groovyc
編譯器編譯代碼(Eclipse Groovy 插件已經(jīng)這樣做了),就會生成一個(gè) Song
.class
文件。
這意味著,如果想在另一個(gè) Groovy 類或 Java 類中使用新建的 Song
類,則必須導(dǎo)入 它(當(dāng)然,除非使用 Song
的代碼與 Song
在同一個(gè)包內(nèi))。
接下來創(chuàng)建一個(gè)新類,名為 SongExample
,將其放在另一個(gè)包結(jié)構(gòu)內(nèi),假設(shè)是 org.thirdparty.lib
。
現(xiàn)在應(yīng)該看到如下所示的代碼:
package org.thirdparty.libclass SongExample { static void main(args) {}}
現(xiàn)在是使用 Song
類的時(shí)候了。首先導(dǎo)入實(shí)例,并將下面的代碼添加到 SongExample
的 main()
方法中。
package org.thirdparty.libimport org.acme.groovy.Songclass SongExample { static void main(args) { def sng = new Song(name:"Le Freak", artist:"Chic", genre:"Disco") }}
現(xiàn)在 Song
實(shí)例創(chuàng)建完成了!但是仔細(xì)看看以前定義的 Song
類的初始化代碼,是否注意到什么特殊之處?您應(yīng)該注意到自動生成了構(gòu)造函數(shù)。
Groovy 自動提供一個(gè)構(gòu)造函數(shù),構(gòu)造函數(shù)接受一個(gè)名稱-值對的映射,這些名稱-值對與類的屬性相對應(yīng)。這是 Groovy 的一項(xiàng)開箱即用的功能 — 用于類中定義的任何屬性,Groovy 允許將存儲了大量值的映射傳給構(gòu)造函數(shù)。映射的這種用法很有意義,例如,您不用初始化對象的每個(gè)屬性。
也可以添加下面這樣的代碼:
def sng2 = new Song(name:"Kung Fu Fighting", genre:"Disco")
也可以像下面這樣直接操縱類的屬性:
def sng3 = new Song()sng3.name = "Funkytown"sng3.artist = "Lipps Inc."sng3.setGenre("Disco") assert sng3.getArtist() == "Lipps Inc."
從這個(gè)代碼中明顯可以看出,Groovy 不僅創(chuàng)建了一個(gè)構(gòu)造函數(shù),允許傳入屬性及其值的映射,還可以通過 .
語法間接地訪問屬性。而且,Groovy 還生成了標(biāo)準(zhǔn)的 setter 和 getter 方法。
在進(jìn)行屬性操縱時(shí),非常有 Groovy 特色的是:總是會調(diào)用 setter 和 getter 方法 — 即使直接通過 .
語法訪問屬性也是如此。
Groovy 是一種本質(zhì)上就很靈活的語言。例如,看看從前面的代碼中將 setGenre()
方法調(diào)用的括號刪除之后會怎么樣,如下所示:
sng3.setGenre "Disco"assert sng3.genre == "Disco"
在 Groovy 中,對于接受參數(shù)的方法,可以省略括號 — 在某些方面,這樣做會讓代碼更容易閱讀。
迄今為止已經(jīng)成功地創(chuàng)建了 Song
類的一些實(shí)例。但是,它們還沒有做什么有趣的事情。可以用以下命令輸出一個(gè)實(shí)例:
println sng3
在 Java 中這樣只會輸出所有對象的默認(rèn) toString()
實(shí)現(xiàn),也就是類名和它的 hashcode(即 org.acme.groovy.Song@44f787
)。下面來看看如何覆蓋默認(rèn)的 toString()
實(shí)現(xiàn),讓輸出效果更好。
在 Song
類中,添加以下代碼:
String toString(){ "${name}, ${artist}, ${genre}"}
根據(jù)本教程已經(jīng)學(xué)到的內(nèi)容,可以省略 toString()
方法上的 public
修改符。仍然需要指定返回類型(String
),以便實(shí)際地覆蓋正確的方法。方法體的定義很簡潔 — 但 return
語句在哪?
您可能已經(jīng)想到:在 Groovy 中可以省略 return
語句。Groovy 默認(rèn)返回方法的最后一行。所以在這個(gè)示例中,返回包含類屬性的 String
。
重新運(yùn)行 SongExample
類,應(yīng)該會看到更有趣的內(nèi)容。toString() 方法返回一個(gè)描述,而不是 hashcode。
Groovy 的自動生成功能對于一些功能來說很方便,但有些時(shí)候需要覆蓋默認(rèn)的行為。例如,假設(shè)需要覆蓋 Song
類中 getGenre()
方法,讓返回的 String
全部為大寫形式。
提供這個(gè)新行為很容易,只要定義 getGenre()
方法即可??梢宰尫椒ǖ穆暶鞣祷?String
,也可以完全省略它(如果愿意)。下面的操作可能是最簡單的:
def getGenre(){ genre.toUpperCase()}
同以前一樣,這個(gè)簡單方法省略了返回類型和 return
語句。現(xiàn)在再次運(yùn)行 SongExample
類。應(yīng)該會看到一些意外的事情 —— 出現(xiàn)了空指針異常。
如果您一直在跟隨本教程,那么應(yīng)該已經(jīng)在 SongExample
類中加入了下面的代碼:
assert sng3.genre == "Disco"
結(jié)果在重新運(yùn)行 SongExample
時(shí)出現(xiàn)了斷言錯(cuò)誤 — 這正是為什么在 Eclipse 控制臺上輸出了丑陋的紅色文字。(很抱歉使用了這么一個(gè)糟糕的技巧)
幸運(yùn)的是,可以輕松地修復(fù)這個(gè)錯(cuò)誤:只要在 SongExample
類中添加以下代碼:
println sng2.artist.toUpperCase()
但是現(xiàn)在控制臺上出現(xiàn)了更多的 紅色文本 — 出什么事了?!
如果回憶一下,就會想起 sng2
實(shí)例沒有定義 artist
值。所以,在調(diào)用 toUpperCase()
方法時(shí)就會生成 Nullpointer
異常。
幸運(yùn)的是, Groovy 通過 操作符提供了一個(gè)安全網(wǎng) — 在方法調(diào)用前面添加一個(gè)
就相當(dāng)于在調(diào)用前面放了一個(gè)條件,可以防止在 null 對象上調(diào)用方法。
例如,將 sng2.artist.toUpperCase()
行替換成 sng2.artist?.toUpperCase()
。請注意,也可以省略后面的括號。(Groovy 實(shí)際上也允許在不帶參數(shù)的方法上省略括號。不過,如果 Groovy 認(rèn)為您要訪問類的屬性而不是方法,那么這樣做可能會造成問題。)
重新運(yùn)行 SongExample
類,您會發(fā)現(xiàn) 操作符很有用。在這個(gè)示例中,沒有出現(xiàn)可惡的異常?,F(xiàn)在將下面的代碼放在這個(gè)類內(nèi),再次運(yùn)行代碼。
def sng4 = new Song(name:"Thriller", artist:"Michael Jackson")println sng4
您將會注意到,雖然預(yù)期可能有異常,但是沒有生成異常。即使沒有定義 genre
,getGenre()
方法也會調(diào)用 toUpperCase()
。
您還記得 Groovy 就是 Java,對吧?所以在 Song
的 toString()
中,引用了 genre
屬性本身,所以不會調(diào)用 getGenre()
?,F(xiàn)在更改 toString()
方法以使用 getGenre()
,然后再看看程序運(yùn)行的結(jié)果。
String toString(){ "${name}, ${artist}, ${getGenre()}"}
重新運(yùn)行 SongExample
,出現(xiàn)類似的異?!,F(xiàn)在,請自己嘗試修復(fù)這個(gè)問題,看看會發(fā)生什么。
希望您做的修改與我的類似。在下面將會看到,我進(jìn)一步擴(kuò)充了 Song
類的 getGenre()
方法,以利用 Groovy 中方便的 操作符。
def getGenre(){ genre?.toUpperCase()}
操作符時(shí)刻都非常有用,可以極大地減少條件語句。
本教程一直都強(qiáng)調(diào) Groovy 只是 Java 的一個(gè)變體。您已經(jīng)看到可以用 Groovy 編寫并使用標(biāo)準(zhǔn)的 Java 程序。為了最后一次證明這點(diǎn),在結(jié)束本教程之前,我們將通過 JUnit 利用 Java 對 Song
類進(jìn)行單元測試。
為了跟上本節(jié)的示例,需要將 JUnit 加入到 Eclipse 項(xiàng)目中。首先,右鍵單擊項(xiàng)目,選擇 Build Path,然后選擇 Add Libraries,如圖 14 所示:
會出現(xiàn) Add Library 對話框,如圖 15 所示。
選擇 JUnit 并單擊 Next 按鈕。應(yīng)該會看到如圖 16 所示的對話框。選擇 JUnit3 或 4— 具體選擇哪項(xiàng)全憑自己決定 — 并單擊 Finish 按鈕。
現(xiàn)在在項(xiàng)目的類路徑中加入了 JUnit,所以能夠編寫 JUnit 測試了。請右鍵單擊 java 源文件夾,選擇 New,然后選擇 JUnit Test Case。定義一個(gè)包,給測試用例命名(例如 SongTest
),在 Class Under Test 部分,單擊 Browse 按鈕。
請注意,可以選擇用 Groovy 定義的 Song
類。圖 17 演示了這一步驟:
選擇該類并單擊 OK(應(yīng)該會看到與圖 18 類似的對話框)并在 New JUnit Test Case 對話框中單擊 Finish 按鈕。
我選擇使用 JUnit 4;所以我定義了一個(gè)名為 testToString()
的測試方法,如下所示:
package org.acme.groovy;import org.junit.Test;public class SongTest { @Test public void testToString(){}}
顯然,需要驗(yàn)證 toString()
方法是否沒有問題,那么第一步該做什么呢?如果想的是 “導(dǎo)入 Song
類”,那么想得就太難了 —Song
類在同一個(gè)包內(nèi),所以第一步是創(chuàng)建它的實(shí)例。
在創(chuàng)建用于測試的 Song
實(shí)例時(shí),請注意不能通過傳給構(gòu)造函數(shù)的映射完全初始化 — 而且,如果想自動完成實(shí)例的 setter 方法,可以看到每個(gè) setter 接受的是 Object
而不是 String
(如圖 19 所示)。為什么會這樣呢?
如果回憶一下,就會記得我在本教程開始的時(shí)候說過:
因?yàn)?Java 中的每個(gè)對象都擴(kuò)展自java.lang.Object
,所以即使在最壞情況下,Groovy 不能確定變量的類型,Groovy 也能將變量的類型設(shè)為Object
然后問題就會迎刃而解。
現(xiàn)在回想一下,在定義 Song
類時(shí),省略了每個(gè)屬性的類型。Groovy 將自然地將每個(gè)屬性的類型設(shè)為 Object
。所以,在標(biāo)準(zhǔn) Java 代碼中使用 Song
類時(shí),看到的 getter 和 setter 的參數(shù)類型和返回類型全都是 Object
。
為了增添樂趣,請打開 Groovy Song
類,將 artist
屬性改為 String
類型,而不是無類型,如下所示:
package org.acme.groovyclass Song { def name String artist def genre String toString(){ "${name}, ${artist}, ${getGenre()}" } def getGenre(){ genre?.toUpperCase() }}
現(xiàn)在,回到 JUnit 測試,在 Song
實(shí)例上使用自動完成功能 — 看到了什么?
在圖 20 中(以及您自己的代碼中,如果一直跟隨本教程的話),setArtist()
方法接受一個(gè) String
,而不是Object
。Groovy 再次證明了它就是 Java,而且應(yīng)用了相同的規(guī)則。
返回來編寫測試,另外請注意,默認(rèn)情況下 Groovy 編譯的類屬性是私有的,所以不能直接在 Java 中訪問它們,必須像下面這樣使用 setter:
@Testpublic void testToString(){ Song sng = new Song(); sng.setArtist("Village People"); sng.setName("Y.M.C.A"); sng.setGenre("Disco"); Assert.assertEquals("Y.M.C.A, Village People, DISCO", sng.toString());}
編寫這個(gè)測試用例余下的代碼就是小菜一碟了。測試用例很好地演示了這樣一點(diǎn):用 Groovy 所做的一切都可以輕易地在 Java 程序中重用,反之亦然。用 Java 語言執(zhí)行的一切操作和編寫的一切代碼,在 Groovy 中也都可以使用。
如果說您從本教程獲得了一個(gè)收獲的話(除了初次體驗(yàn) Groovy 編程之外),那么這個(gè)收獲應(yīng)該是深入地認(rèn)識到 Groovy 就是 Java,只是缺少了您過去使用的許多語法規(guī)則。Groovy 是沒有類型、沒有修改符、沒有 return、沒有 Iterator
、不需要導(dǎo)入集合的 Java。簡而言之,Groovy 就是丟掉了許多包袱的 Java,這些包袱可能會壓垮 Java 項(xiàng)目。
但是在幕后,Groovy 就是 Java。
我希望通向精通 Groovy 的這第一段旅程給您帶來了快樂。您學(xué)習(xí)了 Groovy 語法,創(chuàng)建了幾個(gè)能夠體驗(yàn)到 Groovy 的生產(chǎn)力增強(qiáng)功能的類,看到了用 Java 測試 Groovy 類有多容易。還遇到了第一次使用 Groovy 的開發(fā)者常見的一些問題,看到了如何在不引起太多麻煩的情況下解決它們。
盡管您可能覺得自己目前對 Groovy 還不是很熟練,但您已經(jīng)走出了第一步。您可以用目前學(xué)到的知識編寫自己的第一個(gè) Groovy 程序 — 畢竟,您已經(jīng)設(shè)置好了同時(shí)支持 Groovy 和 Java 編程的雙重環(huán)境!作為有趣的練習(xí),您可以試試用 Gant 設(shè)置下一個(gè)的自動構(gòu)建版本,Gant 是基于 Ant 的構(gòu)建工具,使用 Groovy 來定義構(gòu)建,而不是使用 XML。當(dāng)您對 Groovy 更加適應(yīng)時(shí),可以試著用 Groovy on Grails 構(gòu)建 Web 應(yīng)用程序模塊 — 順便說一下,這是下一篇教程的主題。