java8 函數式編程一

来源:https://www.cnblogs.com/jmcui/archive/2018/12/07/10082310.html
-Advertisement-
Play Games

一、函數介面 |介面|參數|返回類型|描述| |: :|: :|: :|: :| |Predicate<T>|T|boolean|用來比較操作| |Consumer<T>|T|void|沒有返回值的函數| |Function|T|R|有返回值的函數| |Supplier< ...


一、函數介面

介面 參數 返回類型 描述
Predicate<T> T boolean 用來比較操作
Consumer<T> T void 沒有返回值的函數
Function<T, R> T R 有返回值的函數
Supplier<T> None T 工廠方法-返回一個對象
UnaryOperator<T> T T 入參和出參都是相同對象的函數
BinaryOperator<T> (T,T) T 求兩個對象的操作結果

為什麼要先從函數介面說起呢?因為我覺得這是 java8 函數式編程的入口呀!每個函數介面都帶有 @FunctionalInterface 註釋,有且僅有一個未實現的方法,表示接收 Lambda 表達式,它們存在的意義在於將代碼塊作為數據打包起來。

沒有必要過分解讀這幾個函數介面,完全可以把它們看成普通的介面,不過他們有且僅有一個抽象方法(因為要接收 Lambda 表達式啊)。

@FunctionalInterface 該註釋會強制 javac 檢查一個介面是否符合函數介面的標準。 如果該註釋添加給一個枚舉類型、 類或另一個註釋, 或者介面包含不止一個抽象方法, javac 就會報錯。

二、Lambda 表達式

1、Lambda 表達式和匿名內部類

先來複習一下匿名內部類的知識:

  • 如果是介面,相當於在內部返回了一個介面的實現類,並且實現方式是在類的內部進行的;
  • 如果是普通類,匿名類相當於繼承了父類,是一個子類,並可以重寫父類的方法。
  • 需要特別註意的是,匿名類沒有名字,不能擁有一個構造器。如果想為匿名類初始化,讓匿名類獲得一個初始化值,或者說,想使用匿名內部類外部的一個對象,則編譯器要求外部對象為final屬性,否則在運行期間會報錯。
 new Thread(new Runnable() {
     @Override
     public void run() {
         System.out.println(123);
     }
 }).start();
new Thread(()-> System.out.println(123)).start();

如上,和傳入一個實現某介面的對象不同, 我們傳入了一段代碼塊 —— 一個沒有名字的函數。() 是參數列表, 和上面匿名內部類示例中的是一樣的。 -> 將參數和 Lambda 表達式的主體分開, 而主體是之後操作會運行的一些代碼。

Lambda 表達式簡化了匿名內部類的寫法,省略了函數名和參數類型。即參數列表 () 中可以僅指定參數名而不指定參數類型。

Java 是強類型語言,為什麼可以不指定參數類型呢?這得益於 javac 的類型推斷機制,編譯器能夠根據上下文信息推斷出參數的類型,當然也有推斷失敗的時候,這時就需要手動指明參數類型了。javac 的類型推斷機制如下:

  • 對於類中有重載的方法,javac 在推斷類型時,會挑出最具體的類型。
  • 如果只有一個可能的目標類型, 由相應函數介面里的參數類型推導得出;
  • 如果有多個可能的目標類型, 由最具體的類型推導得出;
  • 如果有多個可能的目標類型且最具體的類型不明確, 則需人為指定類型。

2、Lambda 表達式和集合

java8 在 java.util 包中引入了一個新的類 —— Stream.java。java8 之前我們迭代集合,都只能依賴外部迭代器 Iterator 對集合進行串列化處理。而 Stream 支持對集合順序和並行聚合操作,將更多的控制權交給集合類,是一種內部迭代方式。這有利於方便用戶寫出更簡單的代碼,明確要達到什麼轉化,而不是如何轉化。

Stream 的操作有兩種,一種是描述 Stream ,如 filter、map 等最終不產生結果的行為稱為"惰性求值";另外一種像 foreach、collect 等是從 Stream 中產生結果的行為稱為"及早求值"。

接下來讓我們瞧瞧 Stream 如何結合 Lambda 表達式優雅的處理集合...

  • foreach - 迭代集合
list.forEach(e -> System.out.println(e));

map.forEach((k, v) -> {
    System.out.println(k);
    System.out.println(v);
});
  • collect(toList()) - 由Stream里的值生成一個列表。
List<String> list = Stream.of("java", "C++", "Python").collect(Collectors.toList());

等價於:

List<String> asList = Arrays.asList("java", "C++", "Python");
  • filter - 遍歷並檢查過濾其中的元素。
long count = list.stream().filter(x -> "java".equals(x)).count();
  • map、mapToInt、mapToLong、mapToDouble - 將流中的值轉換成一個新的值。
List<String> mapList = list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList());

List<String> list = Stream.of("java", "javascript", "python").collect(Collectors.toList());
IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(e -> e.length()).summaryStatistics();
System.out.println("最大值:" + intSummaryStatistics.getMax());
System.out.println("最小值:" + intSummaryStatistics.getMin());
System.out.println("平均值:" + intSummaryStatistics.getAverage());
System.out.println("總數:" + intSummaryStatistics.getSum());

mapToInt、mapToLong、mapToDouble 和 map 操作類似,只是把函數介面的返回值改為 int、long、double 而已。

  • flatMap - 將多個 Stream 連接成一個 Stream
List<String> streamList = Stream.of(list, asList).flatMap(x -> x.stream()).collect(Collectors.toList());

flatMap 方法的相關函數介面和 map 方法的一樣, 都是 Function 介面, 只是方法的返回值限定為 Stream 類型罷了。

  • Max-求最大值、Min-求最小值
String maxStr = list.stream().max(Comparator.comparing(e -> e.length())).get();
String minStr = list.stream().min(Comparator.comparing(e -> e.length())).get();
  • reduce - 聚合操作,從一組元素中生成一個值,sum()、max()、min()、count() 等都是reduce操作,將他們單獨設為函數只是因為常用。
Integer sum1 = Stream.of(1, 2, 3).reduce(0, (acc, e) -> acc + e);

上述執行求和操作,有兩個參數: 傳入 Stream 中初始值和 acc。 將兩個參數相加,acc 是累加器,保存著當前的累加結果。

  • 待續...

三、預設方法

java8 中新增了 Stream 操作,那麼第三方類庫中的自定義集合 MyList 要怎麼做到相容呢?總不能升級完 java8,第三方類庫中的集合實現全都不能用了吧?

為此,java8 在介面中引入了"預設方法"的概念!預設方法是指介面中定義的包含方法體的方法,方法名有 default 關鍵字做首碼。預設方法的出現是為了 java8 能夠向後相容。

public interface Iterable<T> {
    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
}

看 java8 中的這個 Iterable.java 中的預設方法 forEach(Consumer<? super T> action),表示“如果你們沒有實現 forEach 方法,就使用我的吧”。

預設方法除了添加了一個新的關鍵字 default,在繼承規則上和普通方法也略有差別:

  • 類勝於介面。如果在繼承鏈中有方法體或抽象的方法聲明,那麼就可以忽略介面中定義的方法。
  • 子類勝於父類。果一個介面繼承了另一個介面, 且兩個介面都定義了一個預設方法,那麼子類中定義的方法勝出。
  • 如果上面兩條規則不適用, 子類要麼需要實現該方法, 要麼將該方法聲明為抽象方法。

四、其他

  • 使用 Lambda 表達式,就是將複雜性抽象到類庫的過程。
  • 面向對象編程是對數據進行抽象, 而函數式編程是對行為進行抽象。
  • Java8 雖然在匿名內部類中可以引用非 final 變數, 但是該變數在既成事實上必須是final。即如果你試圖給該變數多次賦值, 然後在 Lambda 表達式中引用它, 編譯器就會報錯。
  • Stream 是用函數式編程方式在集合類上進行複雜操作的工具。
  • 無論何時,將 Lambda 表達式傳給 Stream 上的高階函數,都應該儘量避免副作用。唯一的例外是 forEach 方法,它是一個終結方法。沒有副作用指的是:函數不會改變程式或外界的狀態。
  • 對於需要大量數值運算的演算法來說, 裝箱和拆箱的計算開銷, 以及裝箱類型占用的額外記憶體, 會明顯減緩程式的運行速度。為了減小這些性能開銷, Stream 類的某些方法對基本類型和裝箱類型做了區分。比如 IntStream、LongStream 等。
  • Java8 對為 null 的欄位也引進了自己的處理,既不用一直用 if 判斷對象是否為 null,來看看?
public static List<AssistantVO> getAssistant(Long tenantId) {
    // ofNullable 如果 value 為null,會構建一個空對象。
    Optional<List<AssistantVO>> assistantVO = Optional.ofNullable(ASSISTANT_MAP.get(tenantId));
    // orElse 如果 value 為null,選擇預設對象。 
    assistantVO.orElse(ASSISTANT_MAP.get(DEFAULT_TENANT));
    return assistantVO.get();
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • http://www.pythontutor.com/visualize.html今天去問開發一個Python淺拷貝的問題,開發給了一個神器,可以可視化代碼在記憶體的執行過程,一看即懂,太NB了!~真是理解Python = 淺拷貝 深拷貝的神器。另外這個網站也支持其他語言:Java,JavaScrip ...
  • 用棧來模擬一棵二叉樹的先序遍歷和中序遍歷過程,求這棵二叉樹的後序遍歷 由題棵知道:push是先序遍歷 pop是中序遍歷 ...
  • Python基礎知識(25):常用內建模塊 1、datetime:處理日期和時間 (1)獲取當前日期和時間 (2)獲取指定日期和時間 (3)datetime轉換為timestamp timestamp轉換為datetime timestamp也可以直接被轉換到UTC標準時區的時間 (4)str轉換為 ...
  • 給出一棵樹,問每一層各有多少葉子節點 dfs遍歷樹 bfs遍歷求樹 ...
  • 對象的創建 虛擬機遇到一條new指令時,首先檢查指令的參數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化過。如果沒有,必須先執行相應的類載入過程。 接下來虛擬機為新生對象分配記憶體。對象所需要的記憶體在類載入完成後可以被完全確定,所以只需要把一塊確定大小的 ...
  • 題意:給出倆個整數a,b(不超過10^9) ,求a+b的值 ,並按照xxx,xxx,xxx的格式輸出 ...
  • 實現分散式鎖目前有三種流行方案,分別為基於資料庫、Redis、Zookeeper的方案,其中前兩種方案網路上有很多資料可以參考,本文不做展開。我們來看下使用Zookeeper如何實現分散式鎖。 什麼是Zookeeper? Zookeeper(業界簡稱zk)是一種提供配置管理、分散式協同以及命名的中心 ...
  • logging 日誌模塊 一,級別的概念 二,修改配置信息 #format的多種格式 三,具體需求的實現 #暫時忽略,要用到面向對象的值是,而且可以通過生成器setlevel過濾 更改需求,實現對多個對象不同需求的實現 重點,四,引用配置模塊 #logging.config 專門用於配置loggin ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...