我們經常提到,Java8是革命性的一個版本,原因就是正式引入了函數語言程式設計,那Java的函數語言程式設計在實際應用中到底有什麼用呢?結合實際的應用,我整理出了函數式在Java的幾個經典用途。
惰性求值(Lazy evaluation)是在需要時才進行求值的計算方式。惰性求值自然地在資料結構中包含遞迴,可以以簡單的方式表示無限的概念,這種方式有利於程式的模組化。
在Java這種Strict語言中,我們定義了臨時變數程式碼塊就會立即運算並且取得結果,而實際上很多結果未必會使用到,就存在不必要的計算,如下面的程式碼
/** * 是否是標準或預設工作臺 * 理論上僅判斷預設工作臺即可(因為標準就是預設),此處是兜底一下歷史邏輯 * * @param context * @return */ public boolean isStandardOrDefaultWorkbench(SchemaContext context) { Supplier<Boolean> isDefaultWorkbench = () -> StringUtils.isNotBlank(context.getDefaultAppUuid()) && StringUtils.equals(context.getAppUuid(), context.getDefaultAppUuid()); return WorkbenchType.WORKBENCH.equals(context.getWorkbenchType()) || isDefaultWorkbench.get(); }
當我們使用臨時變數定義的時候,需要理解計算出程式碼
StringUtils.isNotBlank(context.getDefaultAppUuid()) && StringUtils.equals(context.getAppUuid(), context.getDefaultAppUuid())
的值,並用於後面的判斷,不管下面的程式碼是否為True,我們都消耗了一次計算
WorkbenchType.WORKBENCH.equals(context.getWorkbenchType())
而我們使用了Supplier,就可以定義一個匿名錶示式,只有當前面判斷為False的時候,才會執行
isDefaultWorkbench.get()
,而當前面判斷為True的時候,就不會執行後面的程式碼了,通過緩求值的方式,可以節省在某些情況下的消耗,可以提升系統效能,節省資源。
高階函數是指使用其他函數作為引數、或者返回一個函數作為結果的函數。
我們常用的Stream就是典型的流處理思想,可以通過Stream來處理集合並執行相關的操作,其中Stream包含了大量的高階函數,我們可以直接使用匿名錶示式來傳遞業務邏輯。值得注意,匿名錶示式本身返回的就是函數,因此匿名錶示式就是一種高階函數。
// 過濾隱藏應用 return appDetails.stream().filter(app -> { AppModel appModel = new AppModel(app.getAppId(), app.getAppType()); return !appDisplayModel.getHideApps().contains(appModel); }).collect(Collectors.toList());
從上面可以邏輯可以看到,我們可以通過集合的Stream物件進行filter、map、collect操作,其中這些高階函數就可以傳遞lambda表示式,用於處理我們需要的邏輯。
當然除了Stream之外,Java很多工具類也支援外部傳入lambda,比如還有一種常見的工具類Optional。
/** * 獲取組織預設工作臺appUuid * @param corpId * @param orgConfigTag * @return */ public String getDefaultWorkbenchAppUuid(String corpId, OrgConfigTag orgConfigTag) { return Optional.ofNullable(orgConfigTag).map(OrgConfigTag::getDefaultAppUuid) .orElseGet(() -> OpenPageUtils.buildWorkbenchAppUuid(corpId)); }
其中的orElseGet方法就支援外部傳入lambda表示式。
在計算機程式設計中,回撥函數,或簡稱回撥(Callback 即call then back 被主函數呼叫運算後會返回主函數),是指通過引數將函數傳遞到其它程式碼的,某一塊可執行程式碼的參照。 這一設計允許了底層程式碼呼叫在高層定義的子程式。
函數回撥可用於介面兩塊完全無關的邏輯,比如在A模組執行完畢後,執行B模組,B模組的程式碼事先在A模組註冊。很多框架型的邏輯,比如統一結果處理、快取框架、分散式鎖、執行緒快取等都可以通過這個方式來優化,就可以使用lambda的方式來實現。核心的邏輯就是先處理相關的通用的中介軟體邏輯,然後通過lambda來執行業務邏輯。
/** * 建立我的頁面 * * @param corpId * @param uid */ protected void createMyPage(String corpId, Long uid, String appUuid, Method method) throws WorkbenchException { // 並行鎖控制 distributeLockSupport.lockAndDo(key, () -> { //建立頁面方法 userCorpPageSupport.createMyPage(corpId, uid); return null; } ); }
如上面的程式碼,對於要實現分散式鎖控制的模組,可以使用lambda的回撥,來實現加鎖和業務邏輯的分離。其中的定義如下述程式碼所示
/** * 基於快取的分散式鎖操作 * * @param key 資源key * @param callback 回撥函數 * @param <T> 返回結果型別 * @return * @throws Exception */ public <T> T lockAndDo(String key, Callback<T> callback) throws WorkbenchException { //取鎖 ... try { //加鎖 ... return callback.process(); } catch (WorkbenchException ex) { throw ex; } finally { try { //釋放鎖 ... } catch (Exception ex) { //例外處理 } } } //回撥介面定義,供外部傳入lambda @FunctionalInterface public interface Callback<T> { /** * 回撥處理 * * @return 處理結果 * @throws WorkbenchException */ T process() throws WorkbenchException, ServiceException; }
從函數語言程式設計的核心思想來說,函數是一等公民,而物件導向的核心是封裝。可以通過定義函數的方式來降低物件導向指令式程式設計的複雜度。即儘量把處理邏輯都抽象成可複用的函數,並通過lambda的方式進行呼叫。
/** * 批次操作 * * @param consumer 函數式寫法,對批次物件操作 */ public void batchProccess(BiConsumer<Long, OrchardDTO> consumer) { if(orchardDTOMap!=null && orchardDTOMap.size()>0){ for (Map.Entry<Long, OrchardDTO> entry : orchardDTOMap.entrySet()) { consumer.accept(entry.getKey(), entry.getValue()); } } }
public void syncPush(MicroAppContext context, BatchOrchardDTO batchOrchardDTO) { //傳入lambda來執行邏輯 batchOrchardDTO.batchProccess((k, v) -> pushFacadeService.push(k, v)); }
如上圖所示,就是在類中定義了一個高階函數,在呼叫的時候從外部傳入lambda表示式,從而實現batchProccess和pushFacadeService.push兩個方法的解耦。上面的核心和集合支援lambda是一個意思,也就是把需要外部頻繁操作的部分抽象出來定義成函數式介面,以供外部傳入不同的lambda進行豐富的操作。
Java8非常重要的就是引入了函數語言程式設計的思想,使得這門經典的物件導向語言有了函數式的程式設計方式。彌補了很大程度上的不足,函數式思想在處理複雜問題上有著更為令人稱讚的特性。