一:lambada表達(dá)式
說起java8的新特性定续,很多人第一反應(yīng)都是lambada表達(dá)式和流式的API占遥,那么到底什么是lambada表達(dá)式幼东,為什么要引入lambada表達(dá)式颖系,以及引入lambada表達(dá)式為
java8帶來了哪些改變呢愚臀,本文接來下會(huì)一一討論忆蚀。
1. Definition: 什么是lambada表達(dá)式?
直白的先讓大家有個(gè)第一印象,在java8之前馋袜,在創(chuàng)建一個(gè)線程的時(shí)候男旗,我們可能這么寫:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
這段代碼使用了匿名類,Runnable 是一個(gè)接口欣鳖,這里new 了一個(gè)類實(shí)現(xiàn)了 Runnable 接口察皇,然后重寫了 run方法,run方法沒有參數(shù)泽台,方法體也只有一行打印語句什荣。
這段代碼我們其實(shí)只關(guān)心中間打印的語句,其他都是多余的怀酷。
java8后稻爬,我們采用lambada表達(dá)式后,我們就可以簡(jiǎn)寫為:
Runnable r = () -> System.out.println("Hello");
Lambda 表達(dá)式是一種匿名函數(shù)(對(duì) Java 而言這并不完全正確蜕依,但現(xiàn)在姑且這么認(rèn)為)因篇,簡(jiǎn)單地說,它是沒有聲明的方法笔横,也即沒有訪問修飾符竞滓、返回值聲明和名字。
你可以將其想做一種速記吹缔,在你需要使用某個(gè)方法的地方寫上它商佑。當(dāng)某個(gè)方法只使用一次,而且定義很簡(jiǎn)短厢塘,使用這種速記替代之尤其有效茶没,這樣,你就不必在類中費(fèi)力寫聲明與方法了晚碾。
2. lambaba表達(dá)式的語法
lambda 表達(dá)式的語法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
- 一個(gè) Lambda 表達(dá)式可以有零個(gè)或多個(gè)參數(shù)
- 參數(shù)的類型既可以明確聲明抓半,也可以根據(jù)上下文來推斷。例如:(int a)與(a)效果相同
- 所有參數(shù)需包含在圓括號(hào)內(nèi)格嘁,參數(shù)之間用逗號(hào)相隔笛求。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
- 空?qǐng)A括號(hào)代表參數(shù)集為空。例如:() -> 42
- 當(dāng)只有一個(gè)參數(shù)糕簿,且其類型可推導(dǎo)時(shí)探入,圓括號(hào)()可省略。例如:a -> return a*a
- Lambda 表達(dá)式的主體可包含零條或多條語句
- 如果 Lambda 表達(dá)式的主體只有一條語句懂诗,花括號(hào){}可省略蜂嗽。匿名函數(shù)的返回類型與該主體表達(dá)式一致
- 如果 Lambda 表達(dá)式的主體包含一條以上語句,則表達(dá)式必須包含在花括號(hào){}中(形成代碼塊)殃恒。匿名函數(shù)的返回類型與代碼塊的返回類型一致植旧,若沒有返回則為空
以下是lambada表達(dá)式的一些例子:
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
3. 為什么java 會(huì)需要lambada 表達(dá)式辱揭?
Java 是一流的面向?qū)ο笳Z言,除了部分簡(jiǎn)單數(shù)據(jù)類型病附,Java 中的一切都是對(duì)象问窃,即使數(shù)組也是一種對(duì)象,每個(gè)類創(chuàng)建的實(shí)例也是對(duì)象胖喳。
在 Java 中定義的函數(shù)或方法不可能完全獨(dú)立泡躯,也不能將方法作為參數(shù)或返回一個(gè)方法給實(shí)例。
在Java的面向?qū)ο蟮氖澜缋锩胬龊福俺橄蟆笔菍?duì)數(shù)據(jù)的抽象较剃,而“函數(shù)式編程”是對(duì)行為進(jìn)行抽象,在現(xiàn)實(shí)世界中技健,數(shù)據(jù)和行為并存写穴,程序也是如此。
所以java8中l(wèi)ambada表達(dá)式的出現(xiàn)也就彌補(bǔ)java在對(duì)行為進(jìn)行抽象方面的缺失雌贱。
二:函數(shù)式接口
1啊送、Definition: 什么是函數(shù)式接口?
函數(shù)式接口(Functional Interface)是Java 8對(duì)一類特殊類型的接口的稱呼欣孤。 這類接口只定義了唯一的抽象方法的接口(除了隱含的Object對(duì)象的公共方法)馋没,
因此最開始也就做SAM類型的接口(Single Abstract Method)。
首次看到這個(gè)概念的時(shí)候降传,有些迷茫篷朵。因?yàn)榻涌谥械姆椒ǘ际莗ublic abstract 的(即便省略掉這兩個(gè)關(guān)鍵字也是ok的,接口中每一個(gè)方法也是隱式抽象的,接口中的方法會(huì)被隱式的指定為 public abstract(只能是 public abstract婆排,其他修飾符都會(huì)報(bào)錯(cuò)))声旺,那么上面的定義就變成了:只有一個(gè)方法聲明的接口就是函數(shù)式接口。
但是實(shí)際上在代碼中看到的函數(shù)式接口有包含一個(gè)方法的段只,也有包含多個(gè)方法的腮猖,這就讓我迷茫了。
例如下面的兩個(gè)函數(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ù)式接口中除了那個(gè)抽象方法外還可以包含靜態(tài)方法和默認(rèn)方法赞枕。
- Java 8以前的規(guī)范中接口中不允許定義靜態(tài)方法澈缺。 靜態(tài)方法只能在類中定義。 Java 8中可以定義靜態(tài)方法鹦赎。
一個(gè)或者多個(gè)靜態(tài)方法不會(huì)影響SAM接口成為函數(shù)式接口谍椅。 - Java 8中允許接口實(shí)現(xiàn)方法, 而不是簡(jiǎn)單的聲明古话, 這些方法叫做默認(rèn)方法,使用特殊的關(guān)鍵字default锁施。
因?yàn)槟J(rèn)方法不是抽象方法陪踩,所以不影響我們判斷一個(gè)接口是否是函數(shù)式接口杖们。
2、Brief introduction: 函數(shù)式接口簡(jiǎn)介
為什么會(huì)單單從接口中定義出此類接口呢肩狂?
原因是在Java Lambda的實(shí)現(xiàn)中摘完, 開發(fā)組不想再為L(zhǎng)ambda表達(dá)式單獨(dú)定義一種特殊的Structural函數(shù)類型,
稱之為箭頭類型(arrow type)傻谁, 依然想采用Java既有的類型系統(tǒng)(class, interface, method等)孝治, 原因是增加一個(gè)結(jié)構(gòu)化的函數(shù)類型會(huì)增加函數(shù)類型的復(fù)雜性,
破壞既有的Java類型审磁,并對(duì)成千上萬的Java類庫(kù)造成嚴(yán)重的影響谈飒。 權(quán)衡利弊, 因此最終還是利用SAM 接口作為 Lambda表達(dá)式的目標(biāo)類型态蒂。
函數(shù)式接口代表的一種契約杭措, 一種對(duì)某個(gè)特定函數(shù)類型的契約。 在它出現(xiàn)的地方钾恢,實(shí)際期望一個(gè)符合契約要求的函數(shù)手素。
Lambda表達(dá)式不能脫離上下文而存在,它必須要有一個(gè)明確的目標(biāo)類型瘩蚪,而這個(gè)目標(biāo)類型就是某個(gè)函數(shù)式接口泉懦。
換句話說:什么地方可以用lambada表達(dá)式呢? 所有需要FI (Functional Interface)實(shí)例的地方疹瘦,都可以使用lambada表達(dá)式崩哩。
Java 不會(huì)強(qiáng)制要求你使用@FunctionalInterface注解來標(biāo)記你的接口是函數(shù)式接口, 然而拱礁,作為API作者琢锋,
你可能傾向使用@FunctionalInterface指明特定的接口為函數(shù)式接口, 這只是一個(gè)設(shè)計(jì)上的考慮呢灶, 可以讓用戶很明顯的知道一個(gè)接口是函數(shù)式接口吴超。
3、Why: 為什么會(huì)有函數(shù)式接口?
說起函數(shù)式接口的起因就不得不提lambada表達(dá)式鸯乃,說起lambada表達(dá)式的起因就不得不說函數(shù)式編程鲸阻,函數(shù)式編程相比命令式編程有諸多的優(yōu)點(diǎn):(最突出的優(yōu)點(diǎn)有2點(diǎn):
引用透明-->函數(shù)的運(yùn)行不依賴于外部的狀態(tài);沒有副作用-->函數(shù)的運(yùn)行不改變外部的狀態(tài))缨睡,java8為了使用函數(shù)式編程的優(yōu)點(diǎn)鸟悴,從而就使用了lambada表達(dá)式,從而
就定義了一種規(guī)范和約束奖年,這個(gè)規(guī)范和約束就是函數(shù)式接口细诸。
關(guān)于函數(shù)式編程的一些基礎(chǔ)概念會(huì)在下面將。(注意:函數(shù)式編程和函數(shù)式接口是不同的概念陋守。函數(shù)式編程是一種編程范式震贵,與之在同一個(gè)維度的有:命令式編程利赋、邏輯式編程)
4、What: java8里面的函數(shù)式接口都有哪些猩系?
JDK8 之前已經(jīng)有的函數(shù)式接口
- java.lang.Runnable
- java.util.concurrent.callable
- java.awt.event.ActionListener
這里就列舉這幾個(gè)媚送,還有其他的暫時(shí)就不列舉了。
JDK8 新定義的函數(shù)式接口
接口 | 參數(shù) | 返回類型 | 描述 |
---|---|---|---|
Predicate<T> | T | boolean | 用于判別一個(gè)對(duì)象寇甸。比如求一個(gè)人是否為男性 |
Consumer<T> | T | void | 用于接收一個(gè)對(duì)象進(jìn)行處理但沒有返回塘偎,比如接收一個(gè)人并打印他的名字 |
Function<T, R> | T | R | 轉(zhuǎn)換一個(gè)對(duì)象為不同類型的對(duì)象 |
Supplier<T> | None | T | 提供一個(gè)對(duì)象 |
UnaryOperator<T> | T | T | 接收對(duì)象并返回同類型的對(duì)象 |
BinaryOperator<T> | (T, T) | T | 接收兩個(gè)同類型的對(duì)象,并返回一個(gè)原類型對(duì)象 |
- 其中 Cosumer 與 Supplier 對(duì)應(yīng)拿霉,一個(gè)是消費(fèi)者吟秩,一個(gè)是提供者。
- Predicate 用于判斷對(duì)象是否符合某個(gè)條件友浸,經(jīng)常被用來過濾對(duì)象峰尝。
- Function 是將一個(gè)對(duì)象轉(zhuǎn)換為另一個(gè)對(duì)象,比如說要裝箱或者拆箱某個(gè)對(duì)象收恢。
- UnaryOperator 接收和返回同類型對(duì)象武学,一般用于對(duì)對(duì)象修改屬性。BinaryOperator 則可以理解為合并對(duì)象伦意。
如果以前接觸過一些其他 Java 框架火窒,比如 Google Guava,可能已經(jīng)使用過這些接口驮肉,對(duì)這些東西并不陌生熏矿。
三:函數(shù)式編程
1、編程范式
- 命令式編程(Imperative Programming): 專注于”如何去做”离钝,這樣不管”做什么”票编,都會(huì)按照你的命令去做。解決某一問題的具體算法實(shí)現(xiàn)卵渴。
- 函數(shù)式編程(Functional Programming):把運(yùn)算過程盡量寫成一系列嵌套的函數(shù)調(diào)用慧域。
- 邏輯式編程(Logical Programming):它設(shè)定答案須符合的規(guī)則來解決問題,而非設(shè)定步驟來解決問題浪读。過程是事實(shí)+規(guī)則=結(jié)果昔榴。
關(guān)于這個(gè)問題也有一些爭(zhēng)議,有人把函數(shù)式歸結(jié)為聲明式的子集碘橘,還有一些別的七七八八的東西互订,這里就不做闡述了。
聲明式編程:專注于”做什么”而不是”如何去做”痘拆。在更高層面寫代碼仰禽,更關(guān)心的是目標(biāo),而不是底層算法實(shí)現(xiàn)的過程。
如坟瓢, css, 正則表達(dá)式勇边,sql 語句犹撒,html折联,xml…
2、函數(shù)式編程簡(jiǎn)介
相比于命令式編程關(guān)心解決問題的步驟识颊,函數(shù)式編程是面向數(shù)學(xué)的抽象诚镰,關(guān)心數(shù)據(jù)(代數(shù)結(jié)構(gòu))之間的映射關(guān)系。函數(shù)式編程將計(jì)算描述為一種表達(dá)式求值祥款。
在狹義上清笨,函數(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ù)齐苛,這個(gè)術(shù)語不是指命令式編程中的函數(shù),而是指數(shù)學(xué)中的函數(shù)桂塞,即自變量的映射(一種東西和另一種東西之間的對(duì)應(yīng)關(guān)系)凹蜂。
也就是說,一個(gè)函數(shù)的值僅決定于函數(shù)參數(shù)的值阁危,不依賴其他狀態(tài)玛痊。
在函數(shù)式語言中,函數(shù)被稱為一等函數(shù)(First-class function)狂打,與其他數(shù)據(jù)類型一樣擂煞,作為一等公民,處于平等地位菱父,可以在任何地方定義颈娜,在函數(shù)內(nèi)或函數(shù)外;
可以賦值給其他變量浙宜;可以作為參數(shù)官辽,傳入另一個(gè)函數(shù),或者作為別的函數(shù)的返回值粟瞬。
純函數(shù)是這樣一種函數(shù)同仆,即相同的輸入,永遠(yuǎn)會(huì)得到相同的輸出裙品,而且沒有任何可觀察的副作用俗批。
- 不依賴外部狀態(tài)
- 不改變外部狀態(tài)
4俗或、函數(shù)式編程的好處
函數(shù)式的最主要的好處主要是不變性帶來的:
4.1 引用透明(Referential transparency)
引用透明(Referential transparency),指的是函數(shù)的運(yùn)行不依賴于外部變量或”狀態(tài)”岁忘,只依賴于輸入的參數(shù)辛慰,任何時(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)部與外部互動(dòng)(最典型的情況位喂,就是修改全局變量的值)浪耘,產(chǎn)生運(yùn)算以外的其他結(jié)果。
函數(shù)式編程強(qiáng)調(diào)沒有”副作用”忆某,意味著函數(shù)要保持獨(dú)立点待,所有功能就是返回一個(gè)新的值,沒有其他行為弃舒,尤其是不得修改外部變量的值癞埠。
函數(shù)即不依賴外部的狀態(tài)也不修改外部的狀態(tài),函數(shù)調(diào)用的結(jié)果不依賴調(diào)用的時(shí)間和位置聋呢,這樣寫的代碼容易進(jìn)行推理填硕,不容易出錯(cuò)盛嘿。這使得單元測(cè)試和調(diào)試都更容易棵帽。
還有一個(gè)好處是儡首,由于函數(shù)式語言是面向數(shù)學(xué)的抽象,更接近人的語言器贩,而不是機(jī)器語言颅夺,代碼會(huì)比較簡(jiǎn)潔,也更容易被理解蛹稍。
4.3 無鎖并發(fā)
沒有副作用使得函數(shù)式編程各個(gè)獨(dú)立的部分的執(zhí)行順序可以隨意打亂吧黄,(多個(gè)線程之間)不共享狀態(tài),不會(huì)造成資源爭(zhēng)用(Race condition)唆姐,
也就不需要用鎖來保護(hù)可變狀態(tài)拗慨,也就不會(huì)出現(xiàn)死鎖,這樣可以更好地進(jìn)行無鎖(lock-free)的并發(fā)操作。
尤其是在對(duì)稱多處理器(SMP)架構(gòu)下能夠更好地利用多個(gè)處理器(核)提供的并行處理能力赵抢。
4.4 惰性求值
惰性求值(lazy evaluation剧蹂,也稱作call-by-need)是這樣一種技術(shù):是在將表達(dá)式賦值給變量(或稱作綁定)時(shí)并不計(jì)算表達(dá)式的值,
而在變量第一次被使用時(shí)才進(jìn)行計(jì)算烦却。
這樣就可以通過避免不必要的求值提升性能宠叼。
總而言之,函數(shù)式編程由于其不依賴短绸、不改變外部狀態(tài)的基本特性车吹,衍生出了很多其他的有點(diǎn),尤其簡(jiǎn)化了多線程的復(fù)雜性醋闭,提升了高并發(fā)編程的可靠性。
參考資源
版權(quán)聲明:本文為博主原創(chuàng)文章朝卒,遵循 CC 4.0 by-sa 版權(quán)協(xié)議证逻,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
posted by OpenWrite