0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

什么是函數式接口

科技綠洲 ? 來源:Java技術指北 ? 作者:Java技術指北 ? 2023-10-13 14:48 ? 次閱讀

Lambda表達式,相信大家都耳有所聞,而且不少小伙伴在日常的工作中也在使用。但說到函數式接口,可能有一些即使會使用Lambda表達式的小伙伴也會覺得陌生。今天,指北君就將帶領大家對Lambda、及其所使用的一些和函數式接口相關的知識點進行一個全面的學習。函數式接口所涉及的知識點包含:java.util.function包,@FunctoinInterface注解,Lambda表達式,雙冒號操作符。同時,我們還將對函數式接口的實現原理進行深入的剖析。

概述

函數式接口將分為三個篇章來為大家介紹:

  • (應用篇一)(1)函數式接口的來源,(2)Lambda表達式,(3)雙冒號運算符
  • (應用篇二)(4)詳細介紹@FunctionInterface注解(5)對java.util.function包進行解讀
  • (原理篇)介紹函數式接口的實現原理 應用篇將階段相關的JDK源碼以及給出典型的示例代碼 原理篇則從編譯、JVM維度來分析函數式接口的實現原理,具有一定深度,需要讀者具備一定的底層知識。

說明:源碼使用的版本為JDK-11.0.11

什么是函數式接口

【閱讀導引】:本節(jié)為概念性知識,純技術向伙伴可跳過

在分析具體內容之前,指北君帶領大家來對函數式接口做個基本的認知。函數式接口是JAVA語言為引入函數式編程而增加的特性,也即是說函數式接口式Java實現函數式編程的具體方式。那么,函數式編程到底是什么?他和面向對象編程又有什么關系?它能為我們帶來什么?我們又是否真的需要函數式編程?有很多小伙伴,可能和指北君一樣,是以面向對象語言開啟的編程世界的,對于函數式編程其實很陌生。所以,指北君在這里先給大家引薦編程界的三大流派(當然還有別的流派):過程式,函數式,對象式:

圖片
編程范式

函數式編程的思想脫胎于數學理論,也就是我們通常所說的λ演算(λ-calculus)。這也是為什么Java8中引入的函數式編程叫Lambda表達式的原因吧。如同數學中的函數一樣,函數式編程范式中的函數有獨特的特性,也就是通常說的無狀態(tài)或引用透明性。一個函數的輸出由且僅由其輸入決定,同樣的輸入永遠會產生同樣的輸出。

函數式編程的定義:"函數式編程是一種編程范式。它把計算當成是數學函數的求值,從而避免改變狀態(tài)和使用可變數據。它是一種聲明式的編程范式,通過表達式和聲明而不是語句來編程。" 函數式編程的代碼通常更加簡潔,但是不一定易懂。

近年來,隨著多核平臺和并發(fā)計算的發(fā)展,函數式編程的無狀態(tài)特性,在處理這些問題時有著其他編程范式不可比擬的天然優(yōu)勢。這種發(fā)展也就進一步促使了Java引入函數式編程這一特性。

一個簡單示例

指北君先給大家展示一個簡單的函數式編程的示例:

/**
   * 簡單的函數式編程示例
   */
  public static void lambdaDemo1() {
    // 準備測試數據
    Integer[] data = new Integer[] {1, 2, 3};
    List< Integer > list = Arrays.asList(data);
    
    // 簡單示例:轉換單位并打印數據
    list.forEach(x - > System.out.println(String.format("Cents into Yuan: %.2f", x/100.0)));
  }

不熟悉Lambda表達式的小伙伴可能會好奇其中的語句:x -> System.out.println(String.format("Cents into Yuan: %.2f", x/100.0)),這是什么呢?這就是我們的Lambda表達式。通常,我們要訪問List對象,需要通過for、while等控制循環(huán)語句,并在循環(huán)中完成相關工作。有了函數式編程后,我們就可以使用Lambda表達式來完成對應的功能,是不是很簡潔!小伙伴們可能會奇怪,難道Lambda自動做了循環(huán)?當然不是,這里的循環(huán)控制并沒有減少,只是在forEach方法中而已。我們打開默認的迭代器forEach實現方法(ArrayList的forEach實現有差異,總體邏輯一致),代碼顯示forEach循環(huán),并在循環(huán)中執(zhí)行參數的函數邏輯。

default void forEach(Consumer< ? super T > action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

既然沒有省略控制邏輯,難道我們費這么大的力氣引入這個東東就只是為了簡潔點?指北君畫了一下調用邏輯,參見下圖圖片

從上圖中大家是不是隱約可以看出:這種方式可以將控制部分和業(yè)務處理部分進行解耦,業(yè)務處理代碼更容易集中。

我們在分析forEach源碼的時候,看到forEach的參數類型為Consumer,打開Consumer源碼(主要接口聲明部分):

@FunctionalInterface
public interface Consumer< T > {
  ...
}

小伙伴們是不是發(fā)現這就是一個簡單的接口,接口使用了@FunctionInterface的接口,這是不是對Lambda表達式使用位置的約束呢?這個問題我們將在接下來的幾個章節(jié)給出答案。

Lambda表達式

在示例部分,指北君展示了在Java中如何Lambda進行函數式編程,小伙伴們是不是躍躍欲試想要動手了呢?在動手之前指北君先帶領大家了全面學習Lambda表達式的語法。下面給出幾種常見的Lambda代碼片段(代碼僅截取部分,無上下文):

() - > System.out.println("demo")
    ...
    list.forEach(x - >  System.out.print(x));
    ...
    map.forEach((x, y) - > {
      System.out.print(x);
      System.out.println(y);
    });
    ...
    (Integer x, String y) - > System.out.println("x: " + x ", y: " + y);

從上面代碼片段,可以看出Lambda表達式是通過->操作符來連接的,左邊為參數部分,右邊為表達式主體。

Lambda表達式語法:

  1. (parameters)->expression
  2. (parameters)->{statements;}

參數說明:([[type] parameter [, ...]])

  • 參數包括在圓括號內,參數數量可以0到多個,多個參數通過逗號“,”分割,例如(x, y)->
  • 參數類型可明確聲明,也可以省略,省略時根據上下文進行推斷, 例如:(x)->, (int x)->
  • 無參數,直接使用括號,例如:()->
  • 一個參數時,且參數類型省略,則括號可以省略 x->

表達式主體:

  • 由0到多條語句組成
  • 只有一條語句時,語句塊符號“{}”可省略,此時語句的結果將作為返回值,例如:->x*x, ->System.out.print(x)。
  • 超過一條語句時,必須使用語句塊符號“{}”包含起來。
  • 帶return關鍵字必須用代碼塊,例如:->{return x+x}。

常見的組合形式:

(int a, int b) - > {  return a + b; }

() - > System.out.println("Demo")

(String s) - > { 
  System.out.println(s); 
}

() - > 42

() - > { return 3.1415 };

啟動線程

new Thread(
    () - > System.out.println("start in thread.")
).start();

其他的代碼遵循基本的Java語法,小伙伴們現在就可以大展拳腳,試試通過Lambda表達式進行函數式編程。

雙冒號操作符

經過上一節(jié)的實踐,小伙伴們是不是很興奮了,可能有些小伙伴會問,Java類中的的方法也是函數,我可不可以在傳入Lambda表達式的地方傳入普通方法呢?類似下面這種效果:

List< String > list = new ArrayList< String >();
    ...
    list.forEach(xxxMethod());

想法是沒有問題的,但是形式錯誤了,首先xxxMethod()會直接觸發(fā)方法執(zhí)行,并且返回的類型也不匹配forEach方法。那么,正確的形式應該如何寫呢?這就需要我們的雙冒號云算法登場了。雙冒號云算符標準名稱為eta-conversion,有下面四種常用場景

  1. 實例方法引用 object::instanceMethod
  2. 靜態(tài)方法引用 Class::staticMethod
  3. 實例方法引用(實例作為參數傳入) Class::instanceMethod
  4. 構造方法引用 Class:new
  • 無參數:Supplier
  • 一個參數:Function
  • 二個參數:BiFunction
  • 更多:自定義函數接口

示例代碼

public class FunctionInterfaceInvoke {

  public static void main(String[] args) {
    
    // 1-1 構造方法(無參數),編譯會做參數檢查(包含輸入參數和返回值)
    Supplier< FunctionInterfaceInvoke > s = FunctionInterfaceInvoke::new;
    s.get();
    
    //1-2 構造方法(1個參數)
    IntFunction< FunctionInterfaceInvoke > func = FunctionInterfaceInvoke::new;
    func.apply(1);
    
    // 1-3 構造方法(多個參數)
    BiFunction< Integer, Integer, FunctionInterfaceInvoke > func2 = FunctionInterfaceInvoke::new;
    func2.apply(1, 2);
    
    // 2 靜態(tài)方法
    Consumer< Integer > sta1 = FunctionInterfaceInvoke::staticMethod;
    sta1.accept(1);
    
    // 3 實例方法
    IntConsumer sta2 = new FunctionInterfaceInvoke()::instanceMethod;
    sta2.accept(2);

  }

  public FunctionInterfaceInvoke() {
    System.out.println("none parameters");
  }
  
  public FunctionInterfaceInvoke(int p1) {
    System.out.println("constructor whith one parameter: " + p1);
  }
  
  public FunctionInterfaceInvoke(Integer p1, Integer p2) {
    System.out.println(String.format("constructor whith 2 parameters %1s, %2s", p1, p2));
  } 
  
  public static void staticMethod(Integer p1) {
    System.out.println("static method:" + p1);
  }
  
  public void instanceMethod(int p1) {
    System.out.println("instance method:"+p1);
  }
}

小結

函數式接口應用篇的第一部分就給大家介紹到這里,本篇我們介紹了什么是函數式編程,一個簡單示例,Lambda表達式詳細說明和雙冒號操作的使用。下一篇我們將會繼續(xù)介紹函數式接口的應用,學習 @FunctionInterface注解和java.util.function包中的接口。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯系本站處理。 舉報投訴
  • 接口
    +關注

    關注

    33

    文章

    8372

    瀏覽量

    150565
  • 源碼
    +關注

    關注

    8

    文章

    630

    瀏覽量

    29077
  • 函數
    +關注

    關注

    3

    文章

    4262

    瀏覽量

    62243
  • Lambda
    +關注

    關注

    0

    文章

    27

    瀏覽量

    9847
收藏 人收藏

    評論

    相關推薦

    Java基礎教程Java入門到精通day23_06_常用函數接口之Consumer

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 11:25:07

    Java基礎教程Java入門到精通day23_08_常用函數接口之Predicate

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 11:40:10

    Java基礎教程Java入門到精通day23_09_常用函數接口之Predicate

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 11:42:42

    Java基礎教程Java入門到精通day23_02_函數接口作為方法的參數

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 11:56:39

    Java基礎教程Java入門到精通day23_01_函數接口

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 11:58:28

    Java基礎教程Java入門到精通day23_11_常用函數接口之Function

    JAVA
    電子學習
    發(fā)布于 :2023年01月13日 12:17:04

    669.【day29】13 尚硅谷 Java語言高級 函數接口的介紹 #硬聲創(chuàng)作季

    JAVA語言
    充八萬
    發(fā)布于 :2023年07月18日 01:43:57

    Java 8 Stream流底層原理

    初識lambda呢,函數接口肯定是繞不過去的,函數接口就是一個有且僅有一個抽象方法,但是可以
    的頭像 發(fā)表于 11-18 10:27 ?1312次閱讀

    基于JDK 1.8來分析Thread類的源碼

    由上圖我們可以看出,Thread類實現了Runnable接口,而Runnable在JDK 1.8中被@FunctionalInterface注解標記為函數接口,Runnable
    的頭像 發(fā)表于 02-06 17:12 ?567次閱讀

    如何使用lambda表達式提升開發(fā)效率?

    Java8 的一個大亮點是引入 Lambda 表達式,使用它設計的代碼會更加簡潔。當開發(fā)者在編寫 Lambda 表達式時,也會隨之被編譯成一個函數接口
    發(fā)表于 08-24 10:25 ?264次閱讀

    Map+函數接口如何完美的解決if-else問題?

    最近寫了一個服務:根據優(yōu)惠券的類型resourceType和編碼resourceId來 查詢 發(fā)放方式grantType和領取規(guī)則
    的頭像 發(fā)表于 09-07 11:07 ?513次閱讀
    Map+<b class='flag-5'>函數</b><b class='flag-5'>式</b><b class='flag-5'>接口</b>如何完美的解決if-else問題?

    JDK中常見的Lamada表達式

    JDK中有許多函數接口,也會有許多方法會使用函數接口作為參數,同時在各種源碼中也大量使用了這
    的頭像 發(fā)表于 10-10 15:07 ?465次閱讀
    JDK中常見的Lamada表達式

    動態(tài)函數接口的調用原理

    本篇將從編譯,執(zhí)行層面為大家講解函數接口運行的機制,讓各位小伙伴更進一步加深對函數接口的理解
    的頭像 發(fā)表于 10-13 11:27 ?406次閱讀
    動態(tài)<b class='flag-5'>函數</b><b class='flag-5'>接口</b>的調用原理

    函數接口的應用知識點

    概述 函數接口將分為三個篇章來為大家介紹: (應用篇一)(1)函數接口的來源,(2)Lamb
    的頭像 發(fā)表于 10-13 11:32 ?583次閱讀
    <b class='flag-5'>函數</b><b class='flag-5'>式</b><b class='flag-5'>接口</b>的應用知識點

    妙用Java 8中的 Function接口,消滅if...else(非常新穎的寫法)

    使用注解@FunctionalInterface標識,并且只包含一個抽象方法的接口函數接口函數
    的頭像 發(fā)表于 11-10 16:23 ?858次閱讀
    妙用Java 8中的 Function<b class='flag-5'>接口</b>,消滅if...else(非常新穎的寫法)