溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

java8中怎么實現(xiàn)lambada表達式和函數(shù)式編程

發(fā)布時間:2021-07-28 11:32:45 來源:億速云 閱讀:255 作者:Leah 欄目:大數(shù)據(jù)

本篇文章給大家分享的是有關(guān)java8中怎么實現(xiàn)lambada表達式和函數(shù)式編程,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

1. Definition: 什么是lambada表達式?

直白的先讓大家有個第一印象,在java8之前,在創(chuàng)建一個線程的時候,我們可能這么寫:

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

這段代碼使用了匿名類,Runnable 是一個接口,這里new 了一個類實現(xiàn)了 Runnable 接口,然后重寫了 run方法,run方法沒有參數(shù),方法體也只有一行打印語句。 這段代碼我們其實只關(guān)心中間打印的語句,其他都是多余的。 java8后,我們采用lambada表達式后,我們就可以簡寫為:

Runnable r = () -> System.out.println("Hello");

Lambda 表達式是一種匿名函數(shù)(對 Java 而言這并不完全正確,但現(xiàn)在姑且這么認為),簡單地說,它是沒有聲明的方法,也即沒有訪問修飾符、返回值聲明和名字。

你可以將其想做一種速記,在你需要使用某個方法的地方寫上它。當某個方法只使用一次,而且定義很簡短,使用這種速記替代之尤其有效,這樣,你就不必在類中費力寫聲明與方法了。

2. lambaba表達式的語法

lambda 表達式的語法格式如下:

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

  • 一個 Lambda 表達式可以有零個或多個參數(shù)

  • 參數(shù)的類型既可以明確聲明,也可以根據(jù)上下文來推斷。例如:(int a)與(a)效果相同

  • 所有參數(shù)需包含在圓括號內(nèi),參數(shù)之間用逗號相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)

  • 空圓括號代表參數(shù)集為空。例如:() -> 42

  • 當只有一個參數(shù),且其類型可推導(dǎo)時,圓括號()可省略。例如:a -> return a*a

  • Lambda 表達式的主體可包含零條或多條語句

  • 如果 Lambda 表達式的主體只有一條語句,花括號{}可省略。匿名函數(shù)的返回類型與該主體表達式一致

  • 如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括號{}中(形成代碼塊)。匿名函數(shù)的返回類型與代碼塊的返回類型一致,若沒有返回則為空

以下是lambada表達式的一些例子:

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

() -> System.out.println("Hello World");

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

() -> 42

() -> { return 3.1415 };

3. 為什么java 會需要lambada 表達式?

Java 是一流的面向?qū)ο笳Z言,除了部分簡單數(shù)據(jù)類型,Java 中的一切都是對象,即使數(shù)組也是一種對象,每個類創(chuàng)建的實例也是對象。 在 Java 中定義的函數(shù)或方法不可能完全獨立,也不能將方法作為參數(shù)或返回一個方法給實例。

在Java的面向?qū)ο蟮氖澜缋锩?,“抽象”是對?shù)據(jù)的抽象,而“函數(shù)式編程”是對行為進行抽象,在現(xiàn)實世界中,數(shù)據(jù)和行為并存,程序也是如此。 所以java8中l(wèi)ambada表達式的出現(xiàn)也就彌補java在對行為進行抽象方面的缺失。

二:函數(shù)式接口

1、Definition: 什么是函數(shù)式接口?

函數(shù)式接口(Functional Interface)是Java 8對一類特殊類型的接口的稱呼。 這類接口只定義了唯一的抽象方法的接口(除了隱含的Object對象的公共方法), 因此最開始也就做SAM類型的接口(Single Abstract Method)。

首次看到這個概念的時候,有些迷茫。因為接口中的方法都是public abstract 的(即便省略掉這兩個關(guān)鍵字也是ok的,接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)),那么上面的定義就變成了:只有一個方法聲明的接口就是函數(shù)式接口。 但是實際上在代碼中看到的函數(shù)式接口有包含一個方法的,也有包含多個方法的,這就讓我迷茫了。 例如下面的兩個函數(shù)式接口:Runnable 和 Consummer:

Runnable:
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
java.util.function.Consummer:
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

最后才了解了原因在于:函數(shù)式接口中除了那個抽象方法外還可以包含靜態(tài)方法和默認方法。

  • Java 8以前的規(guī)范中接口中不允許定義靜態(tài)方法。 靜態(tài)方法只能在類中定義。 Java 8中可以定義靜態(tài)方法。 一個或者多個靜態(tài)方法不會影響SAM接口成為函數(shù)式接口。

  • Java 8中允許接口實現(xiàn)方法, 而不是簡單的聲明, 這些方法叫做默認方法,使用特殊的關(guān)鍵字default。 因為默認方法不是抽象方法,所以不影響我們判斷一個接口是否是函數(shù)式接口。

參考鏈接: Java 8函數(shù)式接口functional interface的秘密

2、Brief introduction: 函數(shù)式接口簡介

為什么會單單從接口中定義出此類接口呢? 原因是在Java Lambda的實現(xiàn)中, 開發(fā)組不想再為Lambda表達式單獨定義一種特殊的Structural函數(shù)類型, 稱之為箭頭類型(arrow type), 依然想采用Java既有的類型系統(tǒng)(class, interface, method等), 原因是增加一個結(jié)構(gòu)化的函數(shù)類型會增加函數(shù)類型的復(fù)雜性, 破壞既有的Java類型,并對成千上萬的Java類庫造成嚴重的影響。 權(quán)衡利弊, 因此最終還是利用SAM 接口作為 Lambda表達式的目標類型。

函數(shù)式接口代表的一種契約, 一種對某個特定函數(shù)類型的契約。 在它出現(xiàn)的地方,實際期望一個符合契約要求的函數(shù)。 Lambda表達式不能脫離上下文而存在,它必須要有一個明確的目標類型,而這個目標類型就是某個函數(shù)式接口。 換句話說:什么地方可以用lambada表達式呢? 所有需要FI (Functional Interface)實例的地方,都可以使用lambada表達式。

Java 不會強制要求你使用@FunctionalInterface注解來標記你的接口是函數(shù)式接口, 然而,作為API作者, 你可能傾向使用@FunctionalInterface指明特定的接口為函數(shù)式接口, 這只是一個設(shè)計上的考慮, 可以讓用戶很明顯的知道一個接口是函數(shù)式接口。

3、Why: 為什么會有函數(shù)式接口?

說起函數(shù)式接口的起因就不得不提lambada表達式,說起lambada表達式的起因就不得不說函數(shù)式編程,函數(shù)式編程相比命令式編程有諸多的優(yōu)點:(最突出的優(yōu)點有2點: 引用透明-->函數(shù)的運行不依賴于外部的狀態(tài);沒有副作用-->函數(shù)的運行不改變外部的狀態(tài)),java8為了使用函數(shù)式編程的優(yōu)點,從而就使用了lambada表達式,從而 就定義了一種規(guī)范和約束,這個規(guī)范和約束就是函數(shù)式接口。 關(guān)于函數(shù)式編程的一些基礎(chǔ)概念會在下面將。(注意:函數(shù)式編程和函數(shù)式接口是不同的概念。函數(shù)式編程是一種編程范式,與之在同一個維度的有:命令式編程、邏輯式編程)

4、What: java8里面的函數(shù)式接口都有哪些?

JDK8 之前已經(jīng)有的函數(shù)式接口
  • java.lang.Runnable

  • java.util.concurrent.callable

  • java.awt.event.ActionListener 這里就列舉這幾個,還有其他的暫時就不列舉了。

JDK8 新定義的函數(shù)式接口
接口參數(shù)返回類型描述
Predicate<T>Tboolean用于判別一個對象。比如求一個人是否為男性
Consumer<T>Tvoid用于接收一個對象進行處理但沒有返回,比如接收一個人并打印他的名字
Function<T, R>TR轉(zhuǎn)換一個對象為不同類型的對象
Supplier<T>NoneT提供一個對象
UnaryOperator<T>TT接收對象并返回同類型的對象
BinaryOperator<T>(T, T)T接收兩個同類型的對象,并返回一個原類型對象
  • 其中 Cosumer 與 Supplier 對應(yīng),一個是消費者,一個是提供者。

  • Predicate 用于判斷對象是否符合某個條件,經(jīng)常被用來過濾對象。

  • Function 是將一個對象轉(zhuǎn)換為另一個對象,比如說要裝箱或者拆箱某個對象。

  • UnaryOperator 接收和返回同類型對象,一般用于對對象修改屬性。BinaryOperator 則可以理解為合并對象。

如果以前接觸過一些其他 Java 框架,比如 Google Guava,可能已經(jīng)使用過這些接口,對這些東西并不陌生。

三:函數(shù)式編程

1、編程范式

  1. 命令式編程(Imperative Programming): 專注于”如何去做”,這樣不管”做什么”,都會按照你的命令去做。解決某一問題的具體算法實現(xiàn)。

  2. 函數(shù)式編程(Functional Programming):把運算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。

  3. 邏輯式編程(Logical Programming):它設(shè)定答案須符合的規(guī)則來解決問題,而非設(shè)定步驟來解決問題。過程是事實+規(guī)則=結(jié)果。

關(guān)于這個問題也有一些爭議,有人把函數(shù)式歸結(jié)為聲明式的子集,還有一些別的七七八八的東西,這里就不做闡述了。 聲明式編程:專注于”做什么”而不是”如何去做”。在更高層面寫代碼,更關(guān)心的是目標,而不是底層算法實現(xiàn)的過程。 如, css, 正則表達式,sql 語句,html,xml…

2、函數(shù)式編程簡介

相比于命令式編程關(guān)心解決問題的步驟,函數(shù)式編程是面向數(shù)學(xué)的抽象,關(guān)心數(shù)據(jù)(代數(shù)結(jié)構(gòu))之間的映射關(guān)系。函數(shù)式編程將計算描述為一種表達式求值。

在狹義上,函數(shù)式編程意味著沒有可變變量,賦值,循環(huán)和其他的命令式控制結(jié)構(gòu)。即,純函數(shù)式編程語言。

  • Pure Lisp, XSLT, XPath, XQuery, FP

  • Haskell (without I/O Monad or UnsafPerformIO) 在廣義上,函數(shù)式編程意味著專注于函數(shù)

  • Lisp, Scheme, Racket, Clojure

  • SML, Ocaml, F#

  • Haskell (full language)

  • Scala

  • Smalltalk, Ruby

3、“函數(shù)”概念的不同!

函數(shù)式編程中的函數(shù),這個術(shù)語不是指命令式編程中的函數(shù),而是指數(shù)學(xué)中的函數(shù),即自變量的映射(一種東西和另一種東西之間的對應(yīng)關(guān)系)。 也就是說,一個函數(shù)的值僅決定于函數(shù)參數(shù)的值,不依賴其他狀態(tài)。

在函數(shù)式語言中,函數(shù)被稱為一等函數(shù)(First-class function),與其他數(shù)據(jù)類型一樣,作為一等公民,處于平等地位,可以在任何地方定義,在函數(shù)內(nèi)或函數(shù)外; 可以賦值給其他變量;可以作為參數(shù),傳入另一個函數(shù),或者作為別的函數(shù)的返回值。

純函數(shù)是這樣一種函數(shù),即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。

  • 不依賴外部狀態(tài)

  • 不改變外部狀態(tài)

4、函數(shù)式編程的好處

函數(shù)式的最主要的好處主要是不變性帶來的:

4.1 引用透明(Referential transparency)

引用透明(Referential transparency),指的是函數(shù)的運行不依賴于外部變量或”狀態(tài)”,只依賴于輸入的參數(shù),任何時候只要參數(shù)相同, 引用函數(shù)所得到的返回值總是相同的。

其他類型的語言,函數(shù)的返回值往往與系統(tǒng)狀態(tài)有關(guān),不同的狀態(tài)之下,返回值是不一樣的。這就叫”引用不透明”,很不利于觀察和理解程序的行為。

沒有可變的狀態(tài),函數(shù)就是引用透明(Referential transparency)

4.2 沒有副作用(No Side Effect)

副作用(side effect),指的是函數(shù)內(nèi)部與外部互動(最典型的情況,就是修改全局變量的值),產(chǎn)生運算以外的其他結(jié)果。

函數(shù)式編程強調(diào)沒有”副作用”,意味著函數(shù)要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。

函數(shù)即不依賴外部的狀態(tài)也不修改外部的狀態(tài),函數(shù)調(diào)用的結(jié)果不依賴調(diào)用的時間和位置,這樣寫的代碼容易進行推理,不容易出錯。這使得單元測試和調(diào)試都更容易。

還有一個好處是,由于函數(shù)式語言是面向數(shù)學(xué)的抽象,更接近人的語言,而不是機器語言,代碼會比較簡潔,也更容易被理解。

4.3 無鎖并發(fā)

沒有副作用使得函數(shù)式編程各個獨立的部分的執(zhí)行順序可以隨意打亂,(多個線程之間)不共享狀態(tài),不會造成資源爭用(Race condition), 也就不需要用鎖來保護可變狀態(tài),也就不會出現(xiàn)死鎖,這樣可以更好地進行無鎖(lock-free)的并發(fā)操作。

尤其是在對稱多處理器(SMP)架構(gòu)下能夠更好地利用多個處理器(核)提供的并行處理能力。

4.4 惰性求值

惰性求值(lazy evaluation,也稱作call-by-need)是這樣一種技術(shù):是在將表達式賦值給變量(或稱作綁定)時并不計算表達式的值, 而在變量第一次被使用時才進行計算。

這樣就可以通過避免不必要的求值提升性能。

總而言之,函數(shù)式編程由于其不依賴、不改變外部狀態(tài)的基本特性,衍生出了很多其他的有點,尤其簡化了多線程的復(fù)雜性,提升了高并發(fā)編程的可靠性。

以上就是java8中怎么實現(xiàn)lambada表達式和函數(shù)式編程,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI