Groovy 簡單入門

JerryZhou_發表於2012-12-18

一、groovy是什麼

簡單地說,Groovy 是下一代的java語言,跟java一樣,它也執行在 JVM 中。

作為跑在JVM中的另一種語言,groovy語法與 Java 語言的語法很相似。同時,Groovy 拋棄了java煩瑣的文法。同樣的語句,使用groovy能在最大限度上減少你的擊鍵次數--這確實是"懶惰程式設計師們"的福音。

 

二、開發環境

1、  jdk 1.5以上

2、  eclipse+groovy plugin(支援Groovy 1.5.7)

開啟eclipse,通過Software Updates > Find and Install...選單,使用"Search for new features to install" 下載並安裝groovy外掛。New一個遠端站點,url可使用http://dist.codehaus.org/groovy/distributions/update/,外掛名:Groovy plug-in。根據需要你可以同時選擇groovy和grails(後續會學習到):


 

三、建立groovy專案

1、  新建一個groovy專案

New --> Project à Java Project 建立一個java專案。為了方便管理,建議在source中建兩個source資料夾java和groovy,分別用於儲存java原始檔和groovy原始檔:


2、  新增 Groovy 特性

在專案上右擊,Groovy à Add Groovy Nature,這樣會在專案中新增 Groovy Libraries。

 

3、  新增 Groovy 類

在專案groovy原始檔下右鍵,New à Other àGroovy à Groovy Class


自動生成的原始碼如下:

public class HelloWorld{

    /**

     * @param args

     */

    public static void main(def args){

       // TODO Auto-generated method stub

    }  

}

我們在main方法中加一句列印語句:

println "Hello World"

4、  編譯執行groovy類

在原始檔上右鍵,Compile Groovy File,然後右鍵,Run As à Groovy ,在控制檯中檢視執行結果。

實際上 groovy 語法的簡練還體現在,就算整個檔案中只有println "Hello World"這一句程式碼(把除這一句以外的語句刪除掉吧),程式也照樣能夠執行。

當然,為了說明groovy 其實就是java,你也可以完全按照java 語法來編寫HelloWorld類。

 

四、Groovy語法簡介

1、  沒有型別的java

作為動態語言,groovy中所有的變數都是物件(類似於.net framework,所有物件繼承自java.lang.Object),在宣告一個變數時,groovy不要求強制型別宣告,僅僅要求變數名前使用關鍵字def(從groovy jsr 1開始,在以前的版本中,甚至連def都不需要)。

修改main 方法中的程式碼:

def var="hello world"

println var

println var.class

你可以看到程式最後輸出了var的實際型別為:java.lang.String

作為例外,方法引數和迴圈變數的宣告不需要def。

2、  不需要的public

你可以把main方法前面的public去掉,實際上,groovy中預設的修飾符就是public,所以public修飾符你根本就不需要寫,這點跟java不一樣。

3、  不需要的語句結束符

Groovy中沒有語句結束符,當然為了與java保持一致性,你也可以使用;號作為語句結束符。在前面的每一句程式碼後面加上;號結束,程式同樣正常執行(為了接受java程式設計師的頑固習慣)。

4、  字串連線符

跟java一樣,如果你需要把一個字串寫在多行裡,可以使用+號連線字串。程式碼可以這樣寫:

       def var="hello "+

       "world"+

       ",groovy!"

當然更groovy的寫法是:

       def var="""hello

       world

       groovy!"""

三個"號之間不在需要+號進行連線(不過字串中的格式符都會被保留,包括回車和tab)。

5、  一切皆物件

聽起來象是"眾生平等"的味道,事實上groovy對於物件是什麼型別並不關心,一個變數的型別在執行中隨時可以改變,一切根據需要而定。如果你賦給它boolean ,那麼不管它原來是什麼型別,它接受boolean值之後就會自動把型別轉變為boolean值。看下面的程式碼:

def var="hello "+

       "world"+

       ",groovy!"

       println var;

       println var.class;

       var=1001

       println var.class

輸出結果:

hello world,groovy!

class java.lang.String

class java.lang.Integer

 

var這個變數在程式執行中,型別在改變。一開始給它賦值String,它的型別就是String,後面給它賦值Integer,它又轉變為Integer。

6、  迴圈

刪除整個原始檔內容,用以下程式碼替代:

       def var="hello "+

       "world"+

       ",groovy!"

       def repeat(val){

            for(i = 0; i < 5; i++){

             println val

            }

       }

       repeat(var)

輸出:

hello world,groovy!

hello world,groovy!

hello world,groovy!

hello world,groovy!

hello world,groovy!

注意迴圈變數i前面沒有def。當然也沒有java中常見的int,但如果你非要加上int也不會有錯,因為從Groovy1.1beta2之後開始(不包括1.1beta2),groovy開始支援java經典的for迴圈寫法。

此外,上面的for語句還可以寫成:

            for(i in 0..5)

這樣的結果是一樣的。      

7、  String 和 Gstring

除了標準的java.lang.String以外(用'號括住),groovy還支援Gstring字串型別(用"號括住)。把上面的for迴圈中的語句改成:

             println "This is ${i}:${val}"

執行一下,你就會明白什麼是Gstring。

8、  範圍

這個跟pascal中的"子界"是一樣的。在前面的for迴圈介紹中我們已經使用過的for(i in 0..5)這樣的用法,其中的0..5就是一個範圍。

範圍 是一系列的值。例如 "0..4" 表明包含 整數 0、1、2、3、4。Groovy 還支援排除範圍,"0..<4" 表示 0、1、2、3。還可以建立字元範圍:"a..e" 相當於 a、b、c、d、e。"a..<e" 包括小於 e 的所有值。

範圍主要在for迴圈中使用。

9、  預設引數值

可以為方法指定預設引數值。我們修改repeat方法的定義:

def repeat(val,repeat=3){

            for(i in 0..<repeat){

             println "This is ${i}:${val}"

            }

       }

可以看到,repeat方法增加了一個引數repeat(並且給了一個預設值3),用於指定迴圈次數。

當我們不指定第2個引數呼叫repeat方法時,repeat引數取預設值3。

10、              集合

Groovy支援最常見的兩個java集合:java.util.Collection和java.util.Map。前面所說的範圍實際也是集合的一種(java.util.List)。

(1) Collection

Groovy 中這樣來定義一個Collection:

def collect=["a","b","c"]

除了宣告時往集合中新增元素外,還可以用以下方式向集合中新增元素:

collect.add(1);

       collect<<"come on";

       collect[collect.size()]=100.0

Collection使用類似陣列下標的方式進行檢索:

       println collect[collect.size()-1]

       println collect[5]

groovy支援負索引:

println collect[-1]      //索引其倒數第1個元素

       println collect[-2]      //索引其倒數第2個元素

Collection支援集合運算:

collect=collect+5        //在集合中新增元素5

       println collect[collect.size()-1]

       collect=collect-'a'         //在集合中減去元素a(第1個)

       println collect[0]          //現在第1個元素變成b了

同樣地,你可以往集合中新增另一個集合或刪除一個集合:

       collect=collect-collect[0..4]   //把集合中的前5個元素去掉

       println collect[0]   //現在集合中僅有一個元素,即原來的最後一個元素

       println collect[-1]  //也可以用負索引,證明最後一個元素就是第一個元素

(2) Map

Map是"鍵-值"對的集合,在groovy中,鍵不一定是String,可以是任何物件(實際上Groovy中的Map就是java.util.LinkedHashMap)。

如此可以定義一個Map:

       def map=['name':'john','age':14,'sex':'boy']

新增項:

       map=map+['weight':25]       //新增john的體重

       map.put('length',1.27)      //新增john的身高

       map.father='Keller'         //新增john的父親

可以用兩種方式檢索值:

       println map['father']       //通過key作為下標索引

       println map.length          //通過key作為成員名索引

11、              閉包(Closure)

閉包是用{符號括起來的程式碼塊,它可以被單獨執行或呼叫,也可以被命名。類似'匿名類'或行內函數的概念。

閉包中最常見的應用是對集合進行迭代,下面定義了3個閉包對map進行了迭代:

       map.each({key,value->    //key,value兩個引數用於接受每個元素的鍵/值

       println "$key:$value"})

       map.each{println it}     //it是一個關鍵字,代表map集合的每個元素

       map.each({ println it.getKey()+"-->"+it.getValue()})

除了用於迭代之外,閉包也可以單獨定義:

def say={word->

           println "Hi,$word!"

       }

呼叫:

say('groovy')

       say.call('groovy&grails')

輸出:

Hi,groovy!

Hi,groovy&grails!

 

看起來,閉包類似於方法,需要定義引數和要執行的語句,它也可以通過名稱被呼叫。然而閉包物件(不要奇怪,閉包也是物件)可以作為引數傳遞(比如前面的閉包作為引數傳遞給了map的each方法)。而在java中,要做到這一點並不容易(也許C++中的函式指標可以,但不要忘記java中沒有指標)。其次,閉包也可以不命名(當然作為代價,只能在定義閉包時執行一次),而方法不可以。

12、              類

Groovy類和java類一樣,你完全可以用標準java bean的語法定義一個groovy 類。但作為另一種語言,我們可以使用更groovy的方式定義和使用類,這樣的好處是,你可以少寫一半以上的javabean程式碼:

(1)    不需要public修飾符

如前面所言,groovy的預設訪問修飾符就是public,如果你的groovy類成員需要public修飾,則你根本不用寫它。

(2)    不需要型別說明

同樣前面也說過,groovy也不關心變數和方法引數的具體型別。

(3)    不需要getter/setter方法

不要奇怪,在很多ide(如eclipse)早就可以為序員自動產生getter/setter方法了。在groovy中,則徹底不需要getter/setter方法--所有類成員(如果是預設的public)根本不用通過getter/setter方法引用它們(當然,如果你一定要通過get/set方法訪問成員屬性,groovy也提供了它們)。

(4)    不需要建構函式

不在需要程式設計師宣告任何建構函式,因為groovy自動提供了足夠你使用的建構函式。不用擔心建構函式不夠多,因為實際上只需要兩個建構函式(1個不帶引數的預設建構函式,1個只帶一個map引數的建構函式-由於是map型別,通過這個引數你可以在構造物件時任意初始化它的成員變數)。

(5)    不需要return

Groovy中,方法不需要return來返回值嗎?這個似乎很難理解。看後面的程式碼吧。

因此,groovy風格的類是這樣的:

(6)    不需要()號

Groovy中方法呼叫可以省略()號(建構函式除外),也就是說下面兩句是等同的:

 

person1.setName 'kk'person1.setName('kk') 下面看一個完整類定義的例子:

class Person {

 def name

 def age

 String toString(){//注意方法的型別String,因為我們要覆蓋的方法為String型別

     "$name,$age"

 }

如果你使用javabean風格來做同樣的事,起碼程式碼量要增加1倍以上。

我們可以使用預設構造方法例項化Person類:

def person1=new Person()

person1.name='kk'

person1.age=20

println person1

也可以用groovy的風格做同樣的事:

def person2=new Person(['name':'gg','age':22]) //[]號可以省略

println person2

 

這樣需要注意我們覆蓋了Object的toString方法,因為我們想通過println person1這樣的方法簡單地列印物件的屬性值。

然而toString 方法中並沒有return 一個String,但不用擔心,Groovy 預設返回方法的最後一行的值。

13、              ?運算子

在java中,有時候為了避免出現空指標異常,我們通常需要這樣的技巧:

if(rs!=null){

       rs.next()

       … …

}

在groovy中,可以使用?操作符達到同樣的目的:

rs?.next()

?在這裡是一個條件運算子,如果?前面的物件非null,執行後面的方法,否則什麼也不做。

14、              可變引數

等同於java 5中的變長引數。首先我們定義一個變長引數的方法sum:

int sum(int... var) {

def total = 0

for (i in var)

total += i

return total

}

我們可以在呼叫sum時使用任意個數的引數(1個,2個,3個……):

println sum(1)

println sum(1,2)

println sum(1,2,3)

15、              列舉

定義一個enum:

enum Day {

SUNDAY, MONDAY, TUESDAY, WEDNESDAY,

THURSDAY, FRIDAY, SATURDAY

}

然後我們在switch語句中使用他:

def today = Day.SATURDAY

switch (today) {

//Saturday or Sunday

case [Day.SATURDAY, Day.SUNDAY]:

println "Weekends are cool"

break

//a day between Monday and Friday

case Day.MONDAY..Day.FRIDAY:

println "Boring work day"

break

default:

println "Are you sure this is a valid day?"

}

注意,switch和case中可以使用任何物件,尤其是可以在case中使用List和範圍,從而使分支滿足多個條件(這點跟delphi有點象)。

同java5一樣,groovy支援帶構造器、屬性和方法的enum:

enum Planet {

MERCURY(3.303e+23, 2.4397e6),

VENUS(4.869e+24, 6.0518e6),

EARTH(5.976e+24, 6.37814e6),

MARS(6.421e+23, 3.3972e6),

JUPITER(1.9e+27,7.1492e7),

SATURN(5.688e+26, 6.0268e7),

URANUS(8.686e+25, 2.5559e7),

NEPTUNE(1.024e+26, 2.4746e7)

double mass

double radius

Planet(double mass, double radius) {

this.mass = mass;

this.radius = radius;

}

void printMe() {

println "${name()} has a mass of ${mass} " +

"and a radius of ${radius}"

}

}

Planet.EARTH.printMe()

16、              Elvis操作符

這是三目運算子"?:"的簡單形式,三目運算子通常以這種形式出現:

String displayName = name != null ? name : "Unknown";

在groovy中,也可以簡化為(因為null在groovy中可以轉化為布林值false):

String displayName = name ? name : "Unknown";

基於"不重複"的原則,可以使用elvis操作符再次簡化為:

String displayName = name ?: "Unknown"

17、              動態性

Groovy所有的物件都有一個元類metaClass,我們可以通過metaClass屬性訪問該元類。通過元類,可以為這個物件增加方法(在java中不可想象)!見下面的程式碼,msg是一個String,通過元類,我們為msg增加了一個String 類中所沒有的方法up:

def msg = "Hello!"

println msg.metaClass

String.metaClass.up = {  delegate.toUpperCase() }

println msg.up()

通過元類,我們還可以檢索物件所擁有的方法和屬性(就象反射):

msg.metaClass.methods.each { println it.name }

msg.metaClass.properties.each { println it.name }

甚至我們可以看到我們剛才新增的up方法。

我們可以通過元類判斷有沒有一個叫up的方法,然後再呼叫它:

if (msg.metaClass.respondsTo(msg, 'up')) {

    println msg.toUpperCase()

}

當然,也可以推斷它有沒有一個叫bytes的屬性:

if (msg.metaClass.hasProperty(msg, 'bytes')) {

    println msg.bytes.encodeBase64()

}

18、              Groovy swing

到現在為止,我們的groovy一直都在控制檯視窗下工作。如果你還不滿足,當然也可以使用swingbuilder來構建程式:

import groovy.swing.SwingBuilder

import java.awt.BorderLayout

import groovy.swing.SwingBuilder

import java.awt.BorderLayout as BL

def swing = new SwingBuilder()

count = 0

def textlabel

def frame = swing.frame(title:'Frame', size:[300,300]) {

borderLayout()

textlabel = label(text:"Clicked ${count} time(s).",

constraints: BL.NORTH)

button(text:'Click Me',

actionPerformed: {count++; textlabel.text =

"Clicked ${count} time(s)."; println "clicked"},

constraints:BorderLayout.SOUTH)

}

frame.pack()

frame.show()

怎麼樣?是不是跟java 中寫swing程式很象?

 

五、單元測試

1、  新增junit

使用 Build PathàAdd Libraries... 把junit新增到專案中。

2、  新建測試

使用 New à Junit Test Case 新建測試例程:PersonTest,在Class under test右邊的Browser按鈕,選擇要進行測試的groovy類Person。

Finish,下面編寫測試用例程式碼(我使用了Junit4):

import org.junit.*;

public class TestPerson {

       @Test

       public void testToString(){

              Person p=new Person();              //注意因為groovy編譯Person時預設所有屬性為private

              p.setName("ddd");                //所以用set方法設定name屬性而不用p.name="ddd"

              p.setAge(18);

              Assert.assertEquals("ddd-18", p.toString());

       }

}

執行Run AsàJunit Test,發現testToString通過測試。

3、使用groovy書寫測試用例

除了使用Java來書寫測試用例以外,我們也可以使用groovy書寫。

New à Other à Groovy à Groovy Class,寫一個類GroovyTestPerson:

import org.junit.*;

 

class GroovyTestPerson {

    @Test

     void testToString(){

       Person p=new Person("name":"ddd","age":18)

       Assert.assertEquals("ddd-18", p.toString())

    }

}

可以看到,這裡使用的完全是Groovy風格的書寫方式:不需要public,使用map引數構造物件。然而當你Run AsàJunit Test的時候,結果跟用java編寫的測試用例沒有什麼兩樣。

這也充分說明了,groovy和java,除了語法不一樣,本質上沒有什麼區別(對比.net framework中的C#和VB.net,它們除了語法不同外,本質上它們都使用CLR)。