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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...