您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關java8中怎么實現lambada表達式和函數式編程,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
直白的先讓大家有個第一印象,在java8之前,在創建一個線程的時候,我們可能這么寫:
Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } };
這段代碼使用了匿名類,Runnable 是一個接口,這里new 了一個類實現了 Runnable 接口,然后重寫了 run方法,run方法沒有參數,方法體也只有一行打印語句。 這段代碼我們其實只關心中間打印的語句,其他都是多余的。 java8后,我們采用lambada表達式后,我們就可以簡寫為:
Runnable r = () -> System.out.println("Hello");
Lambda 表達式是一種匿名函數(對 Java 而言這并不完全正確,但現在姑且這么認為),簡單地說,它是沒有聲明的方法,也即沒有訪問修飾符、返回值聲明和名字。
你可以將其想做一種速記,在你需要使用某個方法的地方寫上它。當某個方法只使用一次,而且定義很簡短,使用這種速記替代之尤其有效,這樣,你就不必在類中費力寫聲明與方法了。
lambda 表達式的語法格式如下:
(parameters) -> expression 或 (parameters) ->{ statements; }
一個 Lambda 表達式可以有零個或多個參數
參數的類型既可以明確聲明,也可以根據上下文來推斷。例如:(int a)與(a)效果相同
所有參數需包含在圓括號內,參數之間用逗號相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
空圓括號代表參數集為空。例如:() -> 42
當只有一個參數,且其類型可推導時,圓括號()可省略。例如:a -> return a*a
Lambda 表達式的主體可包含零條或多條語句
如果 Lambda 表達式的主體只有一條語句,花括號{}可省略。匿名函數的返回類型與該主體表達式一致
如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括號{}中(形成代碼塊)。匿名函數的返回類型與代碼塊的返回類型一致,若沒有返回則為空
以下是lambada表達式的一些例子:
(int a, int b) -> { return a + b; } () -> System.out.println("Hello World"); (String s) -> { System.out.println(s); } () -> 42 () -> { return 3.1415 };
Java 是一流的面向對象語言,除了部分簡單數據類型,Java 中的一切都是對象,即使數組也是一種對象,每個類創建的實例也是對象。 在 Java 中定義的函數或方法不可能完全獨立,也不能將方法作為參數或返回一個方法給實例。
在Java的面向對象的世界里面,“抽象”是對數據的抽象,而“函數式編程”是對行為進行抽象,在現實世界中,數據和行為并存,程序也是如此。 所以java8中lambada表達式的出現也就彌補java在對行為進行抽象方面的缺失。
函數式接口(Functional Interface)是Java 8對一類特殊類型的接口的稱呼。 這類接口只定義了唯一的抽象方法的接口(除了隱含的Object對象的公共方法), 因此最開始也就做SAM類型的接口(Single Abstract Method)。
首次看到這個概念的時候,有些迷茫。因為接口中的方法都是public abstract 的(即便省略掉這兩個關鍵字也是ok的,接口中每一個方法也是隱式抽象的,接口中的方法會被隱式的指定為 public abstract(只能是 public abstract,其他修飾符都會報錯)),那么上面的定義就變成了:只有一個方法聲明的接口就是函數式接口。 但是實際上在代碼中看到的函數式接口有包含一個方法的,也有包含多個方法的,這就讓我迷茫了。 例如下面的兩個函數式接口:Runnable 和 Consummer:
@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(); }
@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); }; } }
最后才了解了原因在于:函數式接口中除了那個抽象方法外還可以包含靜態方法和默認方法。
Java 8以前的規范中接口中不允許定義靜態方法。 靜態方法只能在類中定義。 Java 8中可以定義靜態方法。 一個或者多個靜態方法不會影響SAM接口成為函數式接口。
Java 8中允許接口實現方法, 而不是簡單的聲明, 這些方法叫做默認方法,使用特殊的關鍵字default。 因為默認方法不是抽象方法,所以不影響我們判斷一個接口是否是函數式接口。
參考鏈接: Java 8函數式接口functional interface的秘密
為什么會單單從接口中定義出此類接口呢? 原因是在Java Lambda的實現中, 開發組不想再為Lambda表達式單獨定義一種特殊的Structural函數類型, 稱之為箭頭類型(arrow type), 依然想采用Java既有的類型系統(class, interface, method等), 原因是增加一個結構化的函數類型會增加函數類型的復雜性, 破壞既有的Java類型,并對成千上萬的Java類庫造成嚴重的影響。 權衡利弊, 因此最終還是利用SAM 接口作為 Lambda表達式的目標類型。
函數式接口代表的一種契約, 一種對某個特定函數類型的契約。 在它出現的地方,實際期望一個符合契約要求的函數。 Lambda表達式不能脫離上下文而存在,它必須要有一個明確的目標類型,而這個目標類型就是某個函數式接口。 換句話說:什么地方可以用lambada表達式呢? 所有需要FI (Functional Interface)實例的地方,都可以使用lambada表達式。
Java 不會強制要求你使用@FunctionalInterface注解來標記你的接口是函數式接口, 然而,作為API作者, 你可能傾向使用@FunctionalInterface指明特定的接口為函數式接口, 這只是一個設計上的考慮, 可以讓用戶很明顯的知道一個接口是函數式接口。
說起函數式接口的起因就不得不提lambada表達式,說起lambada表達式的起因就不得不說函數式編程,函數式編程相比命令式編程有諸多的優點:(最突出的優點有2點: 引用透明-->函數的運行不依賴于外部的狀態;沒有副作用-->函數的運行不改變外部的狀態),java8為了使用函數式編程的優點,從而就使用了lambada表達式,從而 就定義了一種規范和約束,這個規范和約束就是函數式接口。 關于函數式編程的一些基礎概念會在下面將。(注意:函數式編程和函數式接口是不同的概念。函數式編程是一種編程范式,與之在同一個維度的有:命令式編程、邏輯式編程)
java.lang.Runnable
java.util.concurrent.callable
java.awt.event.ActionListener 這里就列舉這幾個,還有其他的暫時就不列舉了。
接口 | 參數 | 返回類型 | 描述 |
---|---|---|---|
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 | 接收兩個同類型的對象,并返回一個原類型對象 |
其中 Cosumer 與 Supplier 對應,一個是消費者,一個是提供者。
Predicate 用于判斷對象是否符合某個條件,經常被用來過濾對象。
Function 是將一個對象轉換為另一個對象,比如說要裝箱或者拆箱某個對象。
UnaryOperator 接收和返回同類型對象,一般用于對對象修改屬性。BinaryOperator 則可以理解為合并對象。
如果以前接觸過一些其他 Java 框架,比如 Google Guava,可能已經使用過這些接口,對這些東西并不陌生。
命令式編程(Imperative Programming): 專注于”如何去做”,這樣不管”做什么”,都會按照你的命令去做。解決某一問題的具體算法實現。
函數式編程(Functional Programming):把運算過程盡量寫成一系列嵌套的函數調用。
邏輯式編程(Logical Programming):它設定答案須符合的規則來解決問題,而非設定步驟來解決問題。過程是事實+規則=結果。
關于這個問題也有一些爭議,有人把函數式歸結為聲明式的子集,還有一些別的七七八八的東西,這里就不做闡述了。 聲明式編程:專注于”做什么”而不是”如何去做”。在更高層面寫代碼,更關心的是目標,而不是底層算法實現的過程。 如, css, 正則表達式,sql 語句,html,xml…
相比于命令式編程關心解決問題的步驟,函數式編程是面向數學的抽象,關心數據(代數結構)之間的映射關系。函數式編程將計算描述為一種表達式求值。
在狹義上,函數式編程意味著沒有可變變量,賦值,循環和其他的命令式控制結構。即,純函數式編程語言。
Pure Lisp, XSLT, XPath, XQuery, FP
Haskell (without I/O Monad or UnsafPerformIO) 在廣義上,函數式編程意味著專注于函數
Lisp, Scheme, Racket, Clojure
SML, Ocaml, F#
Haskell (full language)
Scala
Smalltalk, Ruby
函數式編程中的函數,這個術語不是指命令式編程中的函數,而是指數學中的函數,即自變量的映射(一種東西和另一種東西之間的對應關系)。 也就是說,一個函數的值僅決定于函數參數的值,不依賴其他狀態。
在函數式語言中,函數被稱為一等函數(First-class function),與其他數據類型一樣,作為一等公民,處于平等地位,可以在任何地方定義,在函數內或函數外; 可以賦值給其他變量;可以作為參數,傳入另一個函數,或者作為別的函數的返回值。
純函數是這樣一種函數,即相同的輸入,永遠會得到相同的輸出,而且沒有任何可觀察的副作用。
不依賴外部狀態
不改變外部狀態
函數式的最主要的好處主要是不變性帶來的:
引用透明(Referential transparency),指的是函數的運行不依賴于外部變量或”狀態”,只依賴于輸入的參數,任何時候只要參數相同, 引用函數所得到的返回值總是相同的。
其他類型的語言,函數的返回值往往與系統狀態有關,不同的狀態之下,返回值是不一樣的。這就叫”引用不透明”,很不利于觀察和理解程序的行為。
沒有可變的狀態,函數就是引用透明(Referential transparency)
副作用(side effect),指的是函數內部與外部互動(最典型的情況,就是修改全局變量的值),產生運算以外的其他結果。
函數式編程強調沒有”副作用”,意味著函數要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。
函數即不依賴外部的狀態也不修改外部的狀態,函數調用的結果不依賴調用的時間和位置,這樣寫的代碼容易進行推理,不容易出錯。這使得單元測試和調試都更容易。
還有一個好處是,由于函數式語言是面向數學的抽象,更接近人的語言,而不是機器語言,代碼會比較簡潔,也更容易被理解。
沒有副作用使得函數式編程各個獨立的部分的執行順序可以隨意打亂,(多個線程之間)不共享狀態,不會造成資源爭用(Race condition), 也就不需要用鎖來保護可變狀態,也就不會出現死鎖,這樣可以更好地進行無鎖(lock-free)的并發操作。
尤其是在對稱多處理器(SMP)架構下能夠更好地利用多個處理器(核)提供的并行處理能力。
惰性求值(lazy evaluation,也稱作call-by-need)是這樣一種技術:是在將表達式賦值給變量(或稱作綁定)時并不計算表達式的值, 而在變量第一次被使用時才進行計算。
這樣就可以通過避免不必要的求值提升性能。
總而言之,函數式編程由于其不依賴、不改變外部狀態的基本特性,衍生出了很多其他的有點,尤其簡化了多線程的復雜性,提升了高并發編程的可靠性。
以上就是java8中怎么實現lambada表達式和函數式編程,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。