Java 8 函數式介面和Lambda表達式

来源:https://www.cnblogs.com/ssymon/archive/2022/10/24/16822661.html
-Advertisement-
Play Games

Java 一直是一種面向對象的編程語言。這意味著 Java 編程中的一切都圍繞著對象(為了簡單起見,除了一些基本類型)。我們不僅有 Java 中的函數,它們還是 Class 的一部分,我們需要使用 class/object 來調用任何函數。 函數式介面 當我們研究一些其他的編程語言時,比如C++,J ...


Java 一直是一種面向對象的編程語言。這意味著 Java 編程中的一切都圍繞著對象(為了簡單起見,除了一些基本類型)。我們不僅有 Java 中的函數,它們還是 Class 的一部分,我們需要使用 class/object 來調用任何函數。

函數式介面

當我們研究一些其他的編程語言時,比如C++JavaScript,它們被稱為函數式編程語言,因為我們可以編寫函數併在需要的時候使用它們。其中一些語言支持面向對象編程和函數式編程。

面向對象有很多優勢,但是它也使得程式變得冗長。例如,假設我們必須創建Runnable的一個實例。通常我們使用如下的匿名類:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("This is Runnable");
    }
};

從上面的代碼中我們可以發現,實際使用的部分是run()方法中的代碼。剩下的所有代碼都是因為Java程式結構化的方式。

Java 8函數式介面和Lambda表達式通過刪除大量的固定代碼,幫助我們編寫更少、更簡潔的代碼。

Java 8 函數式介面

有且只有一個抽象方法的介面稱為函數式介面

Java 8引入了@FunctionalInterface註解將介面標記為函數式介面。

不是使用@FunctionalInterface註解的介面才是函數式介面,使用它是為了檢查函數式介面的正確性,使用它是一種規範,就像@Override用來檢查重寫父類或實現介面的方法的正確性。
例如,我們在一個介面之上使用了該註解,併在其中添加多個抽象方法,此時會引發編譯器錯誤。

java.lang.Runnable就是使用單個抽象方法run()的函數式介面的一個很好的例子。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Java 8函數式介面的主要好處是,我們可以使用Lambda表達式來實例化它們,並避免使用笨重的匿名類實現。

Java 8 Collections API 已經被重寫,並且引入了新的Stream API,其中使用了大量的函數式介面。Java 8在java.util.function包中定義了很多函數式介面,一些常用的函數式介面包括ConsumerSupplierFunctionPredicate等等。

下麵是一些代碼片段,以便我們更好的理解函數式介面

interface Test {
    boolean equals(Object obj);
}
//Test不是函數式介面,equals是Object對象的一個成員方法

interface Comparator<T> {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}
//Comparator是函數式介面,Comparator只有一個抽象的非Object的方法

interface Test2 {
    int test2();
    Object clone();
}
//Test2不是函數式介面,因為Object.clone()方法不是public而是protected

interface X {
    int test(String str);
}
interface Y {
    int test(String str);
}
interface Z extends X, Y {
}
//Z是函數式介面,繼承了兩個相同簽名相同返回值的方法

interface X {
    List test(List<String> list);
}
interface Y {
    List<String> test(List list);
}
interface Z extends X, Y {
}
//Z是函數式介面,Y.test 是一個 subsignature & return-type-substitutable
//關於subsignature & return-type-substitutable參考https://www.ssymon.com/archives/subsignature-return-type-substituable
//這裡Y.test簽名是X.test簽名的subsignature,並且Y.test的返回值類型和X.test返回值類型可以相容

interface X {
    int test(List<String> list);
}
interface Y {
    int test(List<Integer> list);
}
interface Z extends X, Y {
}
//Z不是函數式介面,兩個抽象方法沒有一個是subsignature
//雖然方法簽名與泛型無關,但X.test和Y.test無法相容,Z的編譯就會出錯

interface X {
    long test();
}
interface Y {
    int test();
}
interface Z extends X, Y {
}
//編譯出錯:methods have unrelated return types
//Z不是函數式介面,兩個方法返回值不相關不相容,沒有一個是return-type-substitutable

interface A<T> {
    void test(T arg);
}
interface B<T> {
    void test(T arg);
}
interface C<X, Y> extends A<X>, B<Y> {
}
//編譯錯誤:both methods have same erasure, yet neither overrides the other
//C不是函數式介面,兩個方法簽名不同,擦除之後變成相同的原生類型

Lambda表達式

通過Lambda表達式,我們可以在面向Java對象的世界中可視化函數式編程。對象是Java編程語言的基礎,沒有對象就沒有函數,這就是為什麼Java語言只支持在函數式介面中使用Lambda表達式。由於函數式介面中只有一個抽象函數,因此將Lambda表達式應用於該方法時不會出現混淆。

什麼是Lambda表達式:Lambda表達式是一個匿名函數,即沒有函數名的函數。

在Java中:所有函數都是類的成員,被稱為方法。要創建方法,您需要定義其所屬的類。

Lambda表達式語法:箭頭前面部分是方法的參數列表,後一部分方法主體

(parameters) -> expression
或
(parameters) -> { statements; }

Lambda表達式使得我們可以使用非常簡潔的語法定義一個類和單個方法,以實現具有單個抽象方法的介面,即函數式介面

下麵我們用一些代碼來弄明白Lambda表達式如何簡化和縮短代碼,並使得代碼更具備可讀性和可維護性。

實現介面:在Java 8之前,如果要創建線程,首先要定義一個實現可運行介面的類。即函數式介面java.lang.Runnable,其抽象方法run()不接受任何參數。我們需要定義一個實現類來實現它。

public class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("TestRunnable is running");
    }
    public static void main(String[] args) {
        TestRunnable r = new TestRunnable();
        Thread thread = new Thread(r);
        thread.start();
    }
}

在此示例中,我們實現了run()方法將一串字元串列印在控制台。然後創建一個名為 r 的實例對象,並將其傳遞給線程類的構造函數來創建一個線程對象,並調用線程的start方法。

匿名內部類:我們對上面的代碼進行一些改進,使用匿名內部類的方式來實現。

public static void main(String[] args) {
    Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable inner class is running");
        }
    });
    thread1.start();
}

相比實現介面的方式,匿名內部類的方式更加簡潔,無序額外新增實現類。

使用Lambda表達式:在Java 8中,使用Lambda重構的代碼更簡潔,更具可讀性。

public static void main(String[] args) {
    Thread thread = new Thread(() -> System.out.println("Runnable lambda is running"));
    thread.start();
}

從上方的Lambda表達式中我們可以發現:

  • Runnable是一個函數式介面,所以我們可以使用Lambda表達式來創建它的實例。
  • run()方法沒有參數,所以Lambda表達式也沒有參數。
  • 因為方法體中只有一條語句,所以可以不使用大括弧({})。對於多個語句,則必須像其他方法一樣使用大括弧,就像if-else塊一樣。

為什麼需要Lambda表達式

  1. 代碼行數減少,通過對以上的代碼對比我們可以發現使用Lambda表達式可以減少需要編寫的代碼量以及減少必須創建和維護的自定義類的數量。

    比如要實現只使用一次的介面,那麼創建另一個代碼文件或另一個命名類並不總是很有意義。Lambda表達式可以定義一次匿名實現,以供一次性使用,並顯著簡化代碼。

  2. 行為參數化,通過行為參數化來傳遞代碼。

    舉個例子,假如我們對列表中符合給定條件的數字進行求和,使用謂詞方式如下:

    public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
        return numbers.parallelStream()
                .filter(predicate)
                .mapToInt(i -> i)
                .sum();
    }
    

    使用實例:

    List<Integer> numbers = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        numbers.add(i);
    }
    //對所有數字求和
    sumWithCondition(numbers, n -> true);
    //對偶數數字求和
    sumWithCondition(numbers, i -> i % 2 == 0);
    //對大於5的數字求和
    sumWithCondition(numbers, i -> i > 5);
    

    再舉個例子,求出列表中3到11範圍內的最大奇數並返回它的平方:

    public static int findSquareOfMaxOdd(List<Integer> numbers) {
        return numbers.stream()
                .filter(FunctionalInterfaceTest::isOdd)
                .filter(FunctionalInterfaceTest::isGreaterThan3)
                .filter(FunctionalInterfaceTest::isLessThan11)
                .max(Comparator.naturalOrder())
                .map(i -> i * i)
                .get();
    }
    
    public static boolean isOdd(int i) {
        return i % 2 != 0;
    }
    
    public static boolean isGreaterThan3(int i) {
        return i > 3;
    }
    
    public static boolean isLessThan11(int i) {
        return i < 11;
    }
    

    (::)是Java 8的方法引用(即把這個方法作為值),如上面的FunctionalInterfaceTest::isOdd,意思是將isOdd()方法傳遞給filter()方法。它是Lambda表達式i -> isOdd(i)的簡寫形式。

    什麼是謂詞(Predicate)?
    謂詞指的是條件表達式的求值返回true或false的過程,它接受一個參數值並返回true或false。

  3. 在Stream API中使用,後續再詳細介紹Stream API

Lambda表達式示例

() -> {}                     // No parameters; void result

() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body

// Complex block body with multiple returns
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1                 // Single inferred-type argument, same as below
x -> x+1                   // Parenthesis optional for single inferred-type case

(String s) -> s.length()   // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length()              // Single inferred-type argument
t -> { t.start(); }          // Single inferred-type argument

(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

方法引用和構造方法引用示例

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new
關註微信公眾號
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 加密解密是前後端開發經常需要使用到的技術,應用場景包括不限於用戶鑒權、數據傳輸等,不同的應用場景也會需要使用到不同的簽名加密演算法,或者需要搭配不一樣的簽名加密演算法來達到業務目標。所以瞭解加解密,以及常用的加解密函數庫,可以根據不同的業務場景,選擇適合當下業務場景的加解密函數庫。 安全性威脅 這 ...
  • 1 背景 市面上常見的有,2pc/3pc、tcc、saga等常見的分散式事務解決方案,但是實際實施起來框架比較重,設計開發比較繁瑣,不易於快速開發上手。本文提供一種基於柔性事務設計的簡單易上手的分散式事務設計方案,用於解決常見的分散式事務常見場景。 2 常見分散式事務場景 2.1 同步場景 常見的場 ...
  • 循序漸進地介紹了電腦方向的大部分基礎知識,包括計算設備的工作原理、諸多實用技能(包括網頁開發等),適合初學者構建對電腦科學的全面認知。 ...
  • python版本:python 3.9 mutagen版本:1.46.0 mutagen是一個處理音頻元數據的python模塊,支持多種音頻格式,是一個純粹的python庫,僅依賴python標準庫,可在Python 3.7及以上版本運行,支持Linux、Windows 和 macOS系統。 git ...
  • 正則表達式03 5.6正則表達式三個常用類 java.util.regex 包主要包括以下三個類:Pattern類、Matcher類和PatternSyntaxException類 Pattern類 Pattern對象是一個正則表達式對象。Pattern類沒有公共構造方法,要創建一個Pattern對 ...
  • Alwaysblock1 組合邏輯always塊的使用,註意這裡的wire和reg綜合出來的結果是一樣的,這裡只是verilog語法導致二者聲明不一樣。 // synthesis verilog_input_version verilog_2001 module top_module( input ...
  • 目錄 一.OpenGL 反色 1.IOS Object-C 版本 2.Windows OpenGL ES 版本 3.Windows OpenGL 版本 二.OpenGL 反色 GLSL Shader 三.猜你喜歡 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> Op ...
  • 編程教材 《R語言實戰·第2版》Robert I. Kabacoff 課程教材《商務與經濟統計·原書第13版》 (安德森) P143、案例 Go Bananas #1 生產中斷的概率 c <- pbinom(4, 25, .08) # 4 是預設 P(x <= 4) answer1 <- 1 - c ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...