2012年9月29日 星期六

Groovy 的一些好用語法(二) << , 動態屬性 , collect , sort ,

Groovy 是一種動態語言,可以輕易的將方法加到原來死板的 Java 類別中,這樣可以帶來很多好處,有時候太有彈性,甚至用起來有一點頭暈腦脹,所以先整理一下一些好用的方法。

先來講 << 。


List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

<< 看起來很奇怪,但說起來也蠻好理解的,就是「塞過去」。 看箭頭的方向也知道,我要把右邊的塞到左邊。這個是 Java 沒有的語法。 你要是看起來很不習慣,一樣還是可以用 add ,這是 Groovy 的好處,你把他想成加強版的 Java 即可。

new Report 右邊的東西,是把屬性像 map 一樣傳進去。冒號的左邊是key右邊是value。還蠻容易理解的吧。

這邊有一個有趣的地方。一般來說 Java 要動態呼叫一個方法或是屬性,有一點麻煩,也就是說我們「執行時期」才知道要呼叫誰的話,在 Java 是比較不直觀的方式處理的。再一次強調,我的觀點仍然認為,程式的價值不在這些轉換上,所以務必要很容易做到

既然你可以把 map 當建構子傳入,那你可能會想到他是怎麼動態去mapping。
假設你要自己寫也不難,在 Groovy 來說只要寫

def dynamic_prop="total"

assert rpt_list[0]."${dynamic_prop}" == rpt_list[0]."total"
assert rpt_list[0]."total" == rpt_list[0].total

下面二個斷言都會是 true。相信你已經知道 ${ } 是像jsp 裡的變數,Groovy可放在字串中。
所以 rpt_list[0]."${dynamic_prop}"  => 會變成 rpt_list[0]."total"


而造成了可以簡單的動態呼叫的效果。相對於 Java 直接又容易理解。

好了你囉嗦了這麼久,該說 collect 了吧。我們已經準備好剛剛的 List 了

List rpt_list = new ArrayList()
rpt_list << new Report(name:"as1" , m1:10 , m2:20 , total:30)
rpt_list << new Report(name:"as2" , m1:40 , m2:40 , total:80)
rpt_list << new Report(name:"as3" , m1:20 , m2:30 , total:50)

再來你 GSP/JSP 的頁面,老闆要你最上面要先列出一個一個總和。
聰明的你應該想到上一集的招式了

List rpt_total_list = rpt_list*.getTotal()


事情有這麼簡單當然最好,但是老闆改口說,業務要看的是 / 1000 的單位啦,不然值太大。
你抓了抓頭,這樣 「星點」好像不能用了,其實還有更棒的招,就是collect,一行。

List rpt_total_list = rpt_list.collect{ it.total/1000 } 

我紅色標起來的是 Closure,大陸翻閉包,很難聽,就還是叫 Closure好了。
這樣說,閉包是一個物件,這個物件裡要寫一些邏輯,包起來當參數餵給 collect。
collect 裡頭,Groovy 自己幫你實做了,你要餵它的就是閉包 ( 好吧…叫閉包也行 )
collect 收到閉包後他做了什麼事 ?
其實 collect 在上例被呼叫了三次,因為rpt_list 裡有三個物件。
這邊依序取這三個物件的 total 後除 1000 ,然後add 到List 中,有多少加多少,加到完之後結束,把 List 傳出來這樣。
如果用傳統的 Java 來寫,又要容易閱讀的話 :

private List<Report> getTotalList(){
  List rpt_total_list = new ArrayList();
  for(Report r : rpt_list){
     rpt_total_list.add(r.getTotal);
  }
  return rpt_total_list ;
}
//煩死了…一行Groovy比七行Java

閉包使用起來像一個方法,只是不用再獨立寫出來。Groovy 提供了很多對 Collection 的瘋狂技巧。再下來我們講sort。 想到要排序剛剛的sort就很頭痛,因為在 Java 來說,你得這樣寫。你要實作compare來完成這個需求,剛剛的名言又要再講一次,這種東西沒有價值,越好寫越快。價值在客戶要用什麼排序,不是在你怎麼寫出這段程式。
public void sortPeopleByGivenName(List personList) {
  Collections.sort(personList, new Comparator() {
  public int compare(Person p1, Person p2) {
  return p1.getFamilyName().compareTo(p2.getFamilyName());
  }
  });
}
//很煩吧…
用 Groovy 的話
def sortIt(list, property) {
    list.sort { p1, p2 -> 
    p1."${property}" <=> p2."${property}" 
  }
}

長的有一點奇怪,不過你呼叫 sortIt ( rpt_list , "total") 就可以得到你想要的結果了。 上面講過了,那個${}會整個被替換成你丟入的參數。你也注意到了 sort 本身就接受閉包,所以不如改成

rpt_list.sort{o1,o2->
    o1.total <=> o2.total
}

<=> 是啥啦 ! 它就是


public int compare(int i1, int i2) {
 if (i1 == i2) return 0;
 else if (i1 < i2) return -1;
 else return 1;
}

的簡寫啦 ! 所以在閉包裡如果比的是數字,除非你的比法和人家不同,否則 <=> 就行了! 這樣子 sort 你開心了嗎 ? 今天講的就是 collect 與 sort 最後,其實 sort 還可以加條件,剛剛都白寫了 ! 沒錯,Groovy的寫法真的太有彈性了,你還可以寫成

rpt_list.sort{ it.getTotal() } 
是不是很瘋狂呢? 叫你「按照裡面每一個物件的total 給我排序!」 如果你要排的是數字,最後一個方案就是你要的! 一行搞定。

想哭嗎? 很正常。
如果不是單純的排序,你有一些奇怪規則的,留給你自己,在閉包裡動動腦筋囉!

沒有留言:

張貼留言