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

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

什么是函數(shù)式接口

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

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

概述

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

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

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

什么是函數(shù)式接口

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

在分析具體內(nèi)容之前,指北君帶領(lǐng)大家來對函數(shù)式接口做個基本的認(rèn)知。函數(shù)式接口是JAVA語言為引入函數(shù)式編程而增加的特性,也即是說函數(shù)式接口式Java實現(xiàn)函數(shù)式編程的具體方式。那么,函數(shù)式編程到底是什么?他和面向?qū)ο缶幊逃钟惺裁搓P(guān)系?它能為我們帶來什么?我們又是否真的需要函數(shù)式編程?有很多小伙伴,可能和指北君一樣,是以面向?qū)ο笳Z言開啟的編程世界的,對于函數(shù)式編程其實很陌生。所以,指北君在這里先給大家引薦編程界的三大流派(當(dāng)然還有別的流派):過程式,函數(shù)式,對象式:

圖片
編程范式

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

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

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

一個簡單示例

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

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

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

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

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

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

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

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

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

Lambda表達(dá)式

在示例部分,指北君展示了在Java中如何Lambda進(jìn)行函數(shù)式編程,小伙伴們是不是躍躍欲試想要動手了呢?在動手之前指北君先帶領(lǐng)大家了全面學(xué)習(xí)Lambda表達(dá)式的語法。下面給出幾種常見的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表達(dá)式是通過->操作符來連接的,左邊為參數(shù)部分,右邊為表達(dá)式主體。

Lambda表達(dá)式語法:

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

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

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

表達(dá)式主體:

  • 由0到多條語句組成
  • 只有一條語句時,語句塊符號“{}”可省略,此時語句的結(jié)果將作為返回值,例如:->x*x, ->System.out.print(x)。
  • 超過一條語句時,必須使用語句塊符號“{}”包含起來。
  • 帶return關(guān)鍵字必須用代碼塊,例如:->{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語法,小伙伴們現(xiàn)在就可以大展拳腳,試試通過Lambda表達(dá)式進(jìn)行函數(shù)式編程。

雙冒號操作符

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

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

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

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

示例代碼

public class FunctionInterfaceInvoke {

  public static void main(String[] args) {
    
    // 1-1 構(gòu)造方法(無參數(shù)),編譯會做參數(shù)檢查(包含輸入?yún)?shù)和返回值)
    Supplier< FunctionInterfaceInvoke > s = FunctionInterfaceInvoke::new;
    s.get();
    
    //1-2 構(gòu)造方法(1個參數(shù))
    IntFunction< FunctionInterfaceInvoke > func = FunctionInterfaceInvoke::new;
    func.apply(1);
    
    // 1-3 構(gòu)造方法(多個參數(shù))
    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);
  }
}

小結(jié)

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

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

    關(guān)注

    33

    文章

    8257

    瀏覽量

    149953
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    626

    瀏覽量

    28968
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4237

    瀏覽量

    61969
  • Lambda
    +關(guān)注

    關(guān)注

    0

    文章

    26

    瀏覽量

    9830
收藏 人收藏

    評論

    相關(guān)推薦

    Java基礎(chǔ)教程Java入門到精通day23_06_常用函數(shù)接口之Consumer

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

    Java基礎(chǔ)教程Java入門到精通day23_08_常用函數(shù)接口之Predicate

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

    Java基礎(chǔ)教程Java入門到精通day23_09_常用函數(shù)接口之Predicate

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

    Java基礎(chǔ)教程Java入門到精通day23_02_函數(shù)接口作為方法的參數(shù)

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

    Java基礎(chǔ)教程Java入門到精通day23_01_函數(shù)接口

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

    Java基礎(chǔ)教程Java入門到精通day23_11_常用函數(shù)接口之Function

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

    Java 8 Stream流底層原理

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

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

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

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

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

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

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

    JDK中常見的Lamada表達(dá)式

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

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

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

    函數(shù)接口的應(yīng)用知識點

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

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

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