Java8函數語言程式設計應用

2023-12-22 12:00:42

我們經常提到,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非常重要的就是引入了函數語言程式設計的思想,使得這門經典的物件導向語言有了函數式的程式設計方式。彌補了很大程度上的不足,函數式思想在處理複雜問題上有著更為令人稱讚的特性。