Groovy開發人員早已熟知Java 8中新引入的概念和新的語言結構了。在Java新版本即將推 出的增強特性中,有很多是Groovy在幾年前就已經提供了的。從用於函數式編程風格的新語 法,到lambdas表達式、collection streaming和要把方法引用作為一等公民,Groovy開發人 員在未來編寫Java代碼時具有先天性優勢。本文將重點關注Groovy和Java 8的共同點,並闡 述了Java 8如何解讀Groovy中那些熟悉的概念。
我們先來討論一下函數式編程風格, 目前在Groovy中如何使用函數式編程,Java 8的概念如何提供更好的函數式編程風格。
閉包(Closures)也許是Groovy中最好的函數式編程實例了。從內部結構來看, Groovy中的closure只是一個函數式接口實現。函數式接口是指任意只需要實現一個方法的接 口。默認情況下,Groovy的closure實現了一個名為“Callable”的函數式接口,實現了這個 接口的“call”方法。
def closure = { "called" } assert closure instanceofjava.util.concurrent.Callable assert closure() == "called"
通過轉換closure的類型,我們可以讓Groovy實現其他函數式接口。
public interface Function { def apply(); } def closure = { "applied" } as Function assert closure instanceof Function assert closure.apply() == "applied"
在Java 8中很好地引入了閉包和函數式編程的思想。在Java即將發布的版本中函數 式接口極為重要,因為在Java 8中針對新引入的Lambda函數式接口提供了隱含的實現。
我們可以把Lambda函數當成Groovy中的閉包那樣去理解和使用。在Java 8中實現 callable接口像Groovy中的閉包一樣簡單。
Callable callable = () -> "called"; assert callable.call() == "called";
你需要特別注意是,Java 8為單行的lambda函數提供了隱含的返回語句,後來 Groovy也借鑒了這個概念。將來,Groovy也會為單個抽象方法提供隱含實現(類似於Java 8 提供的那些實現)。這個特性使你不必完全派生出closures的具體子類對象就可以使用實例 的屬性和方法。
abstract class WebFlowScope { private static final Map scopeMap = [:] abstractdefgetAttribute(def name); publicdef put(key, val) { scopeMap[key] = val getAttribute(key) } protected Map getScope() { scopeMap } } WebFlowScope closure = { name -> "edited_${scope[name]}" } assert closure instanceofWebFlowScope assert closure.put("attribute", "val") == "edited_val"
Java 8針對帶有接口默認方法的函數式接口提出了一個類似的概念,即Java的新概 念“接口默認方法”。他們希望借此概念在不違反接口實現規約(在Java之前的版本中建立 的實現規約)的前提下改進核心的API。
當把Lambda函數強制轉型為接口時,它們也 可以使用接口的默認方法。也就是說在接口中可以內置健壯的API,使開發人員不必改變類型 的種類或規約就可以使用這些API。
public interface WebFlowScope { static final Map scopeMap = new HashMap(); Object getAttribute(Object key); default public Object put(Object key, Object val) { scopeMap.put(key, val); return getAttribute(key); } default Map getScope() { return scopeMap; } } static final WebFlowScope scope = (Object key) -> "edited_" + scope.getScope().get(key); assert scope.put("attribute", "val") == "val";
Java 8中的接口默認方法還可以幫我們實現像memoization和trampolining這樣的 Groovy特性。你可以很簡單就實現memoization特性,只需要創建一個帶有接口默認方法的函 數式接口,並實現這個默認方法讓它從緩存中確定估算結果或返回結果就可以了。
public interface MemoizedFunction<T, R> { static final Map cache = new HashMap(); R calc(T t); public default R apply(T t) { if (!cache.containsKey(t)) { cache.put(t, calc(t)); } return (R)cache.get(t); } } static final MemoizedFunction<Integer, Integer> fib = (Integer n) -> { if (n == 0 || n == 1) return n; return fib.apply(n - 1)+fib.apply(n-2); }; assert fib.apply(20) == 6765;
同樣,我們還可以使用Java 8的接口默認方法開發Trampoline的實現。Trampoline 是Groovy的一種遞歸策略,這個特性非常適用於深度遞歸,而不可能取代Java的調用棧。
interfaceTrampolineFunction<T, R> { R apply(T...obj); public default Object trampoline(T...objs) { Object result = apply(objs); if (!(result instanceofTrampolineFunction)) { return result; } else { return this; } } } // Wrap the call in a TrampolineFunction so that we can avoid StackOverflowError static TrampolineFunction<Integer, Object> fibTrampoline = (Integer...objs) -> { Integer n = objs[0]; Integer a = objs.length>= 2 ? objs[1] : 0; Integer b = objs.length>= 3 ? objs[2] : 1; if (n == 0) return a; else return fibTrampoline.trampoline(n-1, b, a+b); };
除了closures的基本特性以及那些Memoization和Trampolining的高級特性, Groovy還為Collections API提供了一些有巨大實用價值的語言擴展。我們在使用Groovy時可 以充分利用這些擴展點,比如用list 的“each”方法非常簡捷地完成寫操作。
def list = [1, 2, 3, 4] list.each { item -> println item }
Java 8針對集合的迭代引入了一種與Groovy類似的概念,提供了一個與“each”相 似的“forEach”方法,可以用它取代list傳統的迭代方式。
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); list.forEach( (Integer item) ->System.out.println(item); );
除了簡化list的迭代,Groovy還為應用開發人員提供了各種快捷寫法以簡化各類 list操作。比如“collect”方法,你用這個方法可以將list元素快速映射為新的類型(或新 的值),然後把結果放入新的list裡。
def list = [1, 2, 3, 4] defnewList = list.collect { n -> n * 5 } assert newList == [5, 10, 15, 20]
在Groovy中“collect”的實現比較簡單,你只需要把映射當作一個參數傳遞給 “collect”方法。但是,Java 8的實現就稍微有點復雜了,開發人員可以使用Java 8的 StreamAPI實現同樣的映射和收集策略,實現時要調用“list”的“stream”組件的“map” 方法,然後再調用“map”方法返回的“stream”的“collect”方法。開發人員可以這樣連 續使用Stream API完成list一連串的操作。
List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); List<Integer>newList = list.stream().map((Integer n) -> n * 5).collect (Collectors .toList()); assert newList.get(0) == 5 &&newList.get(1) == 10 &&newList.get(2) == 15 &&newList.get(3) == 20;
Groovy還能讓開發人員使用“findAll”方法簡捷地篩選list。
def emails = ['[email protected]', '[email protected]', '[email protected]', '[email protected]'] defgmails = emails.findAll { it.endsWith('@gmail.com') } assert gmails = ['[email protected]', '[email protected]']
同樣地,Java 8開發人員可以使用Stream API篩選list。
List<String> emails = new ArrayList<>(); emails.add("[email protected]"); emails.add("[email protected]"); emails.add("[email protected]"); emails.add("[email protected]"); List<String>gmails = emails.stream().filter( (String email) ->email.endsWith("@gmail.com") ).collect(Collectors.toList()); assert gmails.get(0) == "[email protected]" &&gmails.get(1) == "[email protected]";
Groovy Collections API擴展還提供了一個“sort”方法,你使用這個方法可以簡 單地完成對list的排序。“sort”方法還可以接受閉包參數,你可以在閉包中實現所需的特 定排序邏輯,閉包會被轉為比較器後完成對list的排序。另外,如果只需要對list進行簡單 地逆序排序,可以調用“reverse”方法反轉list的順序。
def list = [2, 3, 4, 1] assert list.sort() == [1, 2, 3, 4] assert list.sort { a, b -> a-b <=> b } == [1, 4, 3, 2] assert list.reverse() == [2, 3, 4, 1]
再來看Java 8的Stream API,我們可以使用“sorted”方法對list排序,然後用“ toList”方法收集排序結果。“sorted”方法也支持自定義的比較器,它有一個可選的函數 式參數(比如Lambda函數),你可以將自定義的比較器作為參數傳給方法,就可以很容易地 實現特定的排序邏輯和反轉list條目的操作了。
List<Integer> list = new ArrayList<>(); list.add(2); list.add(3); list.add(4); list.add(1); list = list.stream().sorted().collect(Collectors.toList()); assert list.get(0) == 1 &&list.get(3) == 4; list = list.stream().sorted((Integer a, Integer b) <br/>- >Integer.valueOf(a- b).compareTo(b)).collect(Collectors.toList()); assert list.get(0) == 1 &&list.get(1) == 4 &&list.<br/>get (2) == 3 &&list.get(3) == 2; list = list.stream().sorted((Integer a, Integer b) <br/>->b.compareTo (a)).collect (Collectors.toList()); assert list.get(0) == 2 &&list.get(3) == 1;
如果你試圖在一個閉包或Lambda函數內完成所有的處理而連續調用API(比如list streaming),那麼很快就會使代碼難以維護。換一個角度來看,如果你要委托相應工作單元 特定的方法完成特定的處理,那麼這種用法就是一個不錯的選擇了。
我們使用Groovy 時,把方法引用傳給函數也可以實現上面所說的目標。你只要使用“.&”操作符去引用 方法,就可以把該方法強制轉型為閉包傳給另一個方法了。由於可以從外部源碼引入過程代 碼,就從本質上提高了實現的靈活性。這樣,開發人員就可以在邏輯上組織處理方法,完成 更易維護、可持續演進的應用架構了。
def modifier(String item) { "edited_${item}" } def list = ['item1', 'item2', 'item3'] assert list.collect(this.&modifier) == ['edited_item1' , 'edited_item2', 'edited_item3']
Java 8也為開發人員提供了同樣的靈活性,使開發人員可以使用“::”操作符獲得 方法的引用。
List<String> strings = new ArrayList<>(); strings.add("item1"); strings.add("item2"); strings.add("item3"); strings = strings.stream().map(Helpers::modifier). collect(Collectors.toList()); assert "edited_item1".equals(strings.get(0)); assert "edited_item2".equals(strings.get(1)); assert "edited_item3".equals(strings.get(2));
你可以把方法引用傳給任意以函數式接口為形參的方法。那麼,這個方法就會被轉 型為函數式接口,作為函數式接口執行。
public interface MyFunctionalInterface { boolean apply(); } void caller(MyFunctionalInterfacefunctionalInterface) { assert functionalInterface.apply(); } booleanmyTrueMethod() { return true; } caller(Streaming::myTrueMethod);
在Java 8裡,如果類庫開發人員修改了接口規約,那麼這些接口的使用者不必為了 這些變更去修改那些使用了這個類庫的接口。
這些概念和編程風格的無縫轉化是從 Groovy到Java 8的一次具有重要意義的過渡。Groovy為了提高內部靈活性和改進Java原有的 API,使用了大量的JVM空間。隨著這些改進在Java 8裡生根發芽,意味著兩種語言將有更多 的相同點、而不同點會越來越少,事實上這正是本文要介紹的主要內容。當學習和使用這些 新API、新特性和新概念時(從Java 8引入到Java生態系統中),熟練的Groovy開發人員只需 要更短的學習曲線。
查看本欄目