Java 8 vs. Scala(一): Lambda表達(dá)式

【編者按】雖然 Java 深得大量開發(fā)者喜愛漫仆,但是對比其他現(xiàn)代編程語言,其語法確實略顯冗長泪幌。但是通過 Java8盲厌,直接利用 lambda 表達(dá)式就能編寫出既可讀又簡潔的代碼。作者 Hussachai Puripunpinyo 的軟件工程師祸泪,作者通過對比 Java 8和 Scala吗浩,對性能和表達(dá)方面的差異進(jìn)行了分析,并且深入討論關(guān)于 Stream API 的區(qū)別没隘,本文由 OneAPM 工程師編譯整理懂扼。

數(shù)年等待,Java 8 終于添加了高階函數(shù)這個特性右蒲。本人很喜歡 Java阀湿,但不得不承認(rèn),相比其他現(xiàn)代編程語言瑰妄,Java 語法非常冗長陷嘴。然而通過 Java8,直接利用 lambda 表達(dá)式就能編寫出既可讀又簡潔的代碼(有時甚至比傳統(tǒng)方法更具可讀性)间坐。

Java 8于2014年3月3日發(fā)布灾挨,但筆者最近才有機(jī)會接觸。因為筆者也很熟悉 Scala竹宋,所以就產(chǎn)生了對比 Java 8和Scala 在表達(dá)性和性能方面的差異劳澄,比較將圍繞 Stream API 展開,同時也會介紹如何使用 Stream API 來操作集合蜈七。

由于文章太長秒拔,所以分以下三個部分詳細(xì)敘述。

Part 1.Lambda 表達(dá)式

Part 2. Stream API vs Scala collection API

Part 3. Trust no one, bench everything(引用自sbt-jmh)

首先宪潮,我們來了解下 Java 8的 lambda 表達(dá)式溯警,雖然不知道即使表達(dá)式部分是可替代的趣苏,他們卻稱之為 lambda 表達(dá)式。這里完全可以用聲明來代替表達(dá)式梯轻,然后說 Java 8還支持 lambda 聲明食磕。編程語言將函數(shù)作為一等公民,函數(shù)可以被作為參數(shù)或者返回值傳遞喳挑,因為它被視為對象彬伦。Java是一種靜態(tài)的強(qiáng)類型語言。所以伊诵,函數(shù)必須有類型单绑,因此它也是一個接口。

另一方面曹宴,lambda 函數(shù)就是實現(xiàn)了函數(shù)接口的一個類搂橙。無需創(chuàng)建這個函數(shù)的類,編譯器會直接實現(xiàn)笛坦。不幸的是区转,Java 沒有 Scala 那樣高級的類型接口。如果你想聲明一個 lambda 表達(dá)式版扩,就必須指定目標(biāo)類型废离。實際上,由于 Java 必須保持向后兼容性礁芦,這也是可理解的蜻韭,而且就目前來說 Java 完成得很好。例如柿扣,Thread.stop() 在 JDK 1.0版時發(fā)布肖方,已過時了十多年,但即便到今天仍然還在使用窄刘。所以窥妇,不要因為語言 XYZ 的語法(或方法)更好,就指望 Java 從根本上改變語法結(jié)構(gòu)娩践。

所以活翩,Java 8的語言設(shè)計師們奇思妙想,做成函數(shù)接口翻伺!函數(shù)接口是只有一個抽象方法的接口材泄。要知道,大多數(shù)回調(diào)接口已經(jīng)滿足這一要求吨岭。因此拉宗,我們可以不做任何修改重用這些接口。@FunctionalInterface 是表示已注釋接口是函數(shù)接口的注釋。此注釋是可選的旦事,除非有檢查要求魁巩,否則不用再進(jìn)行處理。

請記住姐浮,lambda 表達(dá)式必須定義類型谷遂,而該類型必須只有一個抽象方法。

//Before Java 8
Runnable r = new Runnable(){  
  public void run(){    
    System.out.println(“This should be run in another thread”);  
  }
};

//Java 8
Runnable r = () -> System.out.println(“This should be run in another thread”);

如果一個函數(shù)有一個或多個參數(shù)并且有返回值呢卖鲤?為了解決這個問題肾扰,Java 8提供了一系列通用函數(shù)接口,在java.util.function包里蛋逾。

//Java 8
Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);

該參數(shù)類型可以從函數(shù)中推斷集晚,就像 Java7中的diamond operator,所以可以省略区匣。我們可以重寫該函數(shù)偷拔,如下所示:

//Java 8
Function<String, Integer> parseInt = s -> Integer.parseInt(s);

如果一個函數(shù)有兩個參數(shù)呢?無需擔(dān)心亏钩,Java 8 中有 BiFunction条摸。

//Java 8
BiFunction<Integer, Integer, Integer> multiplier = 
  (i1, i2) -> i1 * i2; //you can’t omit parenthesis here!

如果一個函數(shù)接口有三個參數(shù)呢?TriFunction铸屉?語言設(shè)計者止步于 BiFunction。否則切端,可能真會有 TriFunction彻坛、quadfunction、pentfunction 等踏枣。解釋一下昌屉,筆者是采用 IUPAC 規(guī)則來命名函數(shù)的。然后茵瀑,可以按如下所示定義 TriFunction间驮。

//Java 8
@FunctionalInterface
interface TriFunction<A, B, C, R> {  
  public R apply(A a, B b, C c);
}

然后導(dǎo)入接口,并把它當(dāng)作 lambda 表達(dá)式類型使用马昨。

//Java 8
TriFunction<Integer, Integer, Integer, Integer> sumOfThree 
  = (i1, i2, i3) -> i1 + i2 + i3;

這里你應(yīng)該能理解為什么設(shè)計者止步于 BiFunction竞帽。

如果還沒明白,不妨看看 PentFunction鸿捧,假設(shè)我們在其他地方已經(jīng)定義了 PentFunction屹篓。

//Java 8
PentFunction<Integer, Integer, Integer, Integer, Integer, Integer> 
  sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;

你知道 Ennfunction 是多長嗎?(拉丁語中匙奴,enn 表示9)你必須申報 10 種類型(前 9 個是參數(shù)堆巧,最后一個是返回類型),大概整行都只有類型了。那么聲明一個類型是否有必要呢谍肤?答案是肯定的啦租。(這也是為什么筆者認(rèn)為 Scala 的類型接口比 Java 的更好)

Scala 也有其 lambda 表達(dá)式類型。在 Scala 中荒揣,你可以創(chuàng)建有22個參數(shù)的 lambda 表達(dá)式篷角,意味著 Scala 有每個函數(shù)的類型(Function0、Function1乳附、……Function22)内地。函數(shù)類型在 Scala 函數(shù)中是一個 Trait,Trait 就像 Java 中的抽象類赋除,但可以當(dāng)做混合類型使用阱缓。如果還需要22個以上的參數(shù),那大概是你函數(shù)的設(shè)計有問題举农。必須要考慮所傳遞的一組參數(shù)的類型荆针。在此,筆者將不再贅述關(guān)于 Lambda 表達(dá)式的細(xì)節(jié)颁糟。

下面來看看Scala的其他內(nèi)容航背。Scala 也是類似 Java 的靜態(tài)強(qiáng)類型語言,但它一開始就是函數(shù)語言棱貌。因此玖媚,它能很好地融合面向?qū)ο蠛秃瘮?shù)編程。由于 Scala 和 Java 所采用的方法不同婚脱,這里不能給出 Runnable 的 Scala 實例今魔。Scala 有自己解決問題的方法,所以接下來會詳細(xì)探討障贸。

//Scala
Future(println{“This should be run in another thread”})

與以下 Java8 的代碼等效错森。

//Java 8
//assume that you have instantiated ExecutorService beforehand.
Runnable r = () -> System.out.println(“This should be run in another thread”);
executorService.submit(r);

如果你想聲明一個 lambda 表達(dá)式,可以不用像 Java 那樣聲明一個顯式類型篮洁。

//Java 8
Function<String, Integer> parseInt = s -> Integer.parseInt(s);

//Scala
val parseInt = (s: String) => s.toInt
//or
val parseInt:String => Int = s => s.toInt
//or
val parseInt:Function1[String, Int] = s => s.toInt

所以涩维,在 Scala 中的確有多種辦法來聲明類型。讓編譯器來執(zhí)行袁波。那么 PentFunction 呢瓦阐?

//Java 8
PentFunction<Integer, Integer, Integer, Integer, Integer, Integer> sumOfFive 
  = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;

//Scala
val sumOfFive = (i1: Int, i2: Int, i3: Int, i4: Int, i5: Int) => 
  i1 + i2 + i3 + i4 + i5;

Scala 更短,因為不需要聲明接口類型锋叨,而整數(shù)類型在 Scala 中是 int垄分。短不總意味著更好。Scala 的方法更好娃磺,不是因為短薄湿,而是因為更具可讀性。類型的上下文在參數(shù)列表中,可以很快找出參數(shù)類型豺瘤。如果還不確定吆倦,可以再參考以下代碼。

//Java 8
PentFunction<String, Integer, Double, Boolean, String, String> 
  sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;

//Scala
val sumOfFive = (i1: String, i2: Int, i3: Double, i4: Boolean, i5: String) 
=> i1 + i2 + i3 + i4 + i5;

在 Scala 中坐求,可以很明確地說出 i3 類型是 Double 型蚕泽,但在 Java 8 中,還需要算算它是什么類型桥嗤。你可能爭辯說 Java 也可以须妻,但出現(xiàn)這樣的狀況:

//Java 8
PentFunction<Integer, String, Integer, Double, Boolean, String> sumOfFive 
  = (Integer i1, String i2, Integer i3, Double i4, Boolean i5) 
  -> i1 + i2 + i3 + i4 + i5;

你必須一遍又一遍的重復(fù)下去。

除此之外泛领,Java8 并沒有 PentFunction荒吏,需要自己定義。

//Java 8
@FunctionalInterface
interface PentFunction<A, B, C, D, E, R> {  
  public R apply(A a, B b, C c, D d, E e);
}

是不是意味著 Scala 就更好呢渊鞋?在某些方面的確是绰更。但也有很多地方 Scala 不如 Java。所以很難說到底哪種更好锡宋,我之所以對兩者進(jìn)行比較儡湾,是因為 Scala 是一種函數(shù)語言,而 Java 8 支持一些函數(shù)特點(diǎn)执俩,所以得找函數(shù)語言來比較徐钠。由于 Scala 可以運(yùn)行在 JVM 上,用它來對比再好不過役首〉ぶ澹可能你會在使用函數(shù)時,Scala 有更簡潔的語法和方法宋税,這是因為它本來就是函數(shù)語言,而 Java 的設(shè)計者在不破壞之前的基礎(chǔ)上拓展設(shè)計讼油,顯然會有更多限制杰赛。

盡管 Java在語法上與 lambda 表達(dá)式相比有一定局限性,但 Java8 也引進(jìn)了一些很酷的功能矮台。例如乏屯,利用方法引用的特性通過重用現(xiàn)有方法使得編寫 lambda 表達(dá)式更簡潔。更簡潔嗎瘦赫?辰晕??

//Java 8
Function<String, Integer> parseInt = s -> Integer.parseInt(s);

可以使用方法引用來重寫函數(shù)确虱,如下所示

//Java 8
Function<String, Integer> parseInt = Integer::parseInt;

還可以通過實例方法來使用方法引用含友。之后會在第二部分的 Stream API 中指出這種方法的可用性。

方法引用的構(gòu)造規(guī)則

1.(args) -> ClassName.staticMethod(args);

可以像這樣重寫ClassName::staticMethod;

Function<Integer, String> intToStr = String::valueOf;

2.(instance, args) -> instance.instanceMethod(args);

可以像這樣重寫 ClassName::instanceMethod;

BiFunction<String,String, Integer> indexOf = String::indexOf;

3.(args) -> expression.instanceMethod(args);

可以像這樣重寫 expression::instanceMethod窘问;

Function<String, Integer>indexOf = new String()::indexOf;

你有沒有注意到規(guī)則2有點(diǎn)奇怪辆童?有點(diǎn)混亂?盡管 indexOf 函數(shù)只需要1個參數(shù)惠赫,但 BiFunction 的目標(biāo)類型是需要2個參數(shù)把鉴。其實,這種用法通常在 Stream API 中使用儿咱,當(dāng)看不到類型名時才有意義庭砍。

pets.stream().map(Pet::getName).collect(toList());
// The signature of map() function can be derived as
// <String> Stream<String> map(Function<? super Pet, ? extends String> mapper)

從規(guī)則3中,你可能會好奇能否用 lambda 表達(dá)式替換 new String()?

你可以用這種方法構(gòu)造一個對象

Supplier<String> str =String::new;

那么可以這樣做嗎?

Function<Supplier<String>,Integer> indexOf = (String::new)::indexOf;

不能混埠。它不能編譯怠缸,編譯器會提示The target type of this expression must be a functional interface。錯誤信息很容易引起誤解岔冀,而且似乎 Java 8通過泛型參數(shù)并不支持類型接口凯旭。即使使用一個 Functionalinterface 的實例(如前面提到的「STR」),也會出現(xiàn)另一個錯誤The type Supplier<String> does not define indexOf(Supplier<String>) that is applicable here使套。String::new 的函數(shù)接口是 Supplier<String>罐呼,而且它只有方法命名為 get()。indexOf 是一個屬于 String 對象的實例方法侦高。因此嫉柴,必須重寫這段代碼,如下所示奉呛。

//Java
Function<String, Integer> indexOf =          ((Supplier<String>)String::new).get()::indexOf;

Java 8 是否支持 currying (partial function)计螺?

的確可行,但你不能使用方法引用瞧壮。你可以認(rèn)為是一個 partial 函數(shù)登馒,但是它返回的是函數(shù)而不是結(jié)果。接著將要介紹使用 currying 的簡單實例咆槽,但這個例子也可能行不通陈轿。在傳遞到函數(shù)之前,我們通常進(jìn)行參數(shù)處理秦忿。但無論如何麦射,先看看如何利用 lambda 表達(dá)式實現(xiàn) partial 函數(shù)。假設(shè)你需要利用 currying 實現(xiàn)兩個整數(shù)相加的函數(shù)灯谣。

//Java
IntFunction<IntUnaryOperator>add = a -> b -> a + b;
add.apply(2).applyAsInt(3);//the result is 4! I'm kidding it's 5.

該函數(shù)可以同時采用兩個參數(shù)潜秋。

//Java
Supplier<BiFunction<Integer,Integer, Integer>> add = () -> (a, b) -> a + b;
add.get().apply(2, 3);

現(xiàn)在,可以看看 Scala 方法胎许。

//Scala
val add = (a: Int) => (b:Int) => a + b
add(1)(2)

//Scala
val add = () => (a: Int,b: Int) => a + b
add2()(1,2)

因為類型引用和語法糖峻呛,Scala 的方法比 Java 更簡短罗售。在 Scala 中,你不需要在 Function trait 上調(diào)用 apply 方法杀饵,編譯器會即時地將()轉(zhuǎn)換為 apply 方法莽囤。

原文鏈接: https://dzone.com/articles/java-8-λe-vs-scalapart-i

OneAPM for Java 能夠深入到所有 Java 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級別性能問題的可見性切距、性能瓶頸的快速識別與追溯朽缎、真實用戶體驗監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理谜悟。想閱讀更多技術(shù)文章话肖,請訪問 OneAPM 官方博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葡幸,一起剝皮案震驚了整個濱河市最筒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蔚叨,老刑警劉巖床蜘,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蔑水,居然都是意外死亡邢锯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門搀别,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丹擎,“玉大人,你說我怎么就攤上這事歇父〉倥啵” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵榜苫,是天一觀的道長护戳。 經(jīng)常有香客問我,道長垂睬,這世上最難降的妖魔是什么灸异? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮羔飞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘檐春。我一直安慰自己逻淌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布疟暖。 她就那樣靜靜地躺著卡儒,像睡著了一般田柔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骨望,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天硬爆,我揣著相機(jī)與錄音,去河邊找鬼擎鸠。 笑死缀磕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的劣光。 我是一名探鬼主播袜蚕,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绢涡!你這毒婦竟也來了牲剃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤雄可,失蹤者是張志新(化名)和其女友劉穎凿傅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體数苫,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡聪舒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了文判。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片过椎。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戏仓,靈堂內(nèi)的尸體忽然破棺而出疚宇,到底是詐尸還是另有隱情,我是刑警寧澤赏殃,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布敷待,位于F島的核電站,受9級特大地震影響仁热,放射性物質(zhì)發(fā)生泄漏榜揖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一抗蠢、第九天 我趴在偏房一處隱蔽的房頂上張望举哟。 院中可真熱鬧,春花似錦迅矛、人聲如沸妨猩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壶硅。三九已至威兜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庐椒,已是汗流浹背椒舵。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留约谈,地道東北人笔宿。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像窗宇,于是被迫代替她去往敵國和親措伐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容