摘要:之前,使用匿名類給蘋果排序的代碼是的,這段代碼看上去并不是那么的清晰明了,使用表達(dá)式改進(jìn)后或者是不得不承認(rèn),代碼看起來跟清晰了。這是由泛型接口內(nèi)部實(shí)現(xiàn)方式造成的。
# Lambda表達(dá)式
在《Java8實(shí)戰(zhàn)》中第三章主要講的是Lambda表達(dá)式,在上一章節(jié)的筆記中我們利用了行為參數(shù)化來因?qū)Σ粩嘧兓男枨螅詈笪覀円彩褂玫搅薒ambda,通過表達(dá)式為我們簡化了很多代碼從而極大地提高了我們的效率。那我們就來更深入的了解一下如何使用Lambda表達(dá)式,讓我們的代碼更加具有簡潔性和易讀性。
什么是Lambda表達(dá)式?簡單的來說,Lambda表達(dá)式是一個(gè)匿名函數(shù),Lambda表達(dá)式基于數(shù)學(xué)中的λ演算得名,直接對應(yīng)其中的Lambda抽象(lambda abstraction),是一個(gè)匿名函數(shù),既沒有函數(shù)名的函數(shù)。Lambda表達(dá)式可以表示閉包(注意和數(shù)學(xué)傳統(tǒng)意義的不同)。你也可以理解為,簡潔的表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表、函數(shù)主體、返回類型,可能還有一個(gè)可以拋出異常的列表。
有時(shí)候,我們?yōu)榱撕喕a而去使用匿名類,雖然匿名類能簡化一部分代碼,但是看起來很啰嗦。為了更好的的提高開發(fā)的效率以及代碼的簡潔性和可讀性,Java8推出了一個(gè)核心的新特性之一:Lambda表達(dá)式。
Java8之前,使用匿名類給蘋果排序的代碼:
apples.sort(new Comparator() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight().compareTo(o2.getWeight()); } });
是的,這段代碼看上去并不是那么的清晰明了,使用Lambda表達(dá)式改進(jìn)后:
ComparatorbyWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
或者是:
ComparatorbyWeight = Comparator.comparing(Apple::getWeight);
不得不承認(rèn),代碼看起來跟清晰了。要是你覺得Lambda表達(dá)式看起來一頭霧水的話也沒關(guān)系,我們慢慢的來了解它。
現(xiàn)在,我們來看看幾個(gè)Java8中有效的Lambda表達(dá)式加深對Lambda表達(dá)式的理解:
// 這個(gè)表達(dá)式具有一個(gè)String類型的參數(shù)并返回一個(gè)int,Lambda并沒有return語句,因?yàn)橐呀?jīng)隱含了return。 (String s) -> s.length() // 這個(gè)表達(dá)式有一個(gè)Apple類型的參數(shù)并返回一個(gè)boolean(蘋果重來是否大于150克) (Apple a) -> a.getWeight() > 150 // 這個(gè)表達(dá)式具有兩個(gè)int類型二的參數(shù)并且沒有返回值。Lambda表達(dá)式可以包含多行代碼,不只是這兩行。 (int x, int y) -> { System.out.println("Result:"); System.out.println(x + y); } // 這個(gè)表達(dá)式?jīng)]有參數(shù)類型,返回一個(gè)int。 () -> 250 // 顯式的指定為Apple類型,并對重量進(jìn)行比較返回int (Apple a2, Apple a2) -> a1.getWeight.compareTo(a2.getWeight())
Java語言設(shè)計(jì)者選選擇了這樣的語法,是因?yàn)镃#和Scala等語言中的類似功能廣受歡迎。Lambda的基本語法是:
(parameters) -> expression
或者(請注意花括號):
(parameters) -> {statements;}
是的,Lambda表達(dá)式的語法看起來就是那么簡單。那我們繼續(xù)看幾個(gè)例子,看看以下哪幾個(gè)是有效的:
(1) () -> {} (2) () -> "Jack" (3) () -> {return "Jack"} (4) (Interge i) -> return "Alan" + i; (5) (String s) -> {"IronMan";}
正確答案是:(1)、(2)、(3)
原因:
(1) 是一個(gè)無參并且無返回的,類似與private void run() {}.
(2) 是一個(gè)無參并且返回的是一個(gè)字符串。
(3) 是一個(gè)無參,并且返回的是一個(gè)字符串,不過里面還可以繼續(xù)寫一些其他的代碼(利用顯式返回語句)。
(4) 它沒有使用使用顯式返回語句,所以它不能算是一個(gè)表達(dá)式。想要有效就必須加一對花括號,
(Interge i) -> {return "Alan" + i}
(5) "IronMan"很顯然是一個(gè)表達(dá)式,不是一個(gè)語句,去掉這一對花括號或者使用顯式返回語句即可有效。
在哪里以及如何使用Lambda我們剛剛已經(jīng)看了很多關(guān)于Lambda表達(dá)式的語法例子,可能你還不太清楚這個(gè)Lambda表達(dá)式到底如何使用。
還記得在上一章的讀書筆記中,實(shí)現(xiàn)的filter方法中,我們使用的就是Lambda:
ListheavyApples = filter(apples, (Apple apple) -> apple.getWeight() > 150);
我們可以在函數(shù)式接口上使用Lambda表達(dá)式,函數(shù)式接口聽起來很抽象,但是不用太擔(dān)心接下來就會解釋函數(shù)式接口是什么。
函數(shù)式接口還記得第二章中的讀書筆記,為了參數(shù)化filter方法的行為使用的Predicate
public interface Comparable{ public int compareTo(T o); } public interface Runnable { public abstract void run(); } public interface Callable { V call() throws Exception; }
當(dāng)然,不只是它們,還有很多一些其他的函數(shù)式接口。
函數(shù)式接口到底可以用來干什么?Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)例,并把整個(gè)表達(dá)式作為函數(shù)式接口的實(shí)例(具體來說,是函數(shù)式接口一個(gè)具體實(shí)現(xiàn)的實(shí)例)。你也可以使用匿名類實(shí)現(xiàn),只不過看來并不是那么的一目了然。使用匿名類你需要提供一個(gè)實(shí)例,然后在直接內(nèi)聯(lián)將它實(shí)例化。
通過下面的代碼,你可以來比較一下使用函數(shù)式接口和使用匿名類的區(qū)別:
// 使用Lambda表達(dá)式 Runnable r1 = () -> System.out.println("HelloWorld 1"); // 使用匿名類 Runnable r2 = new Runnable() { @Override public void run() { System.out.println("HelloWorld 2"); } }; // 運(yùn)行結(jié)果 System.out.println("Runnable運(yùn)行結(jié)果:"); // HelloWorld 1 process(r1); // HelloWorld 2 process(r2); // HelloWorld 3 process(() -> System.out.println("HelloWorld 3")); private static void process(Runnable r) { r.run(); }
酷,從上面的代碼可以看出使用Lambda表達(dá)式你可以減少很多代碼同時(shí)也提高了代碼的可讀性而使用匿名類卻要四五行左右的代碼。
函數(shù)描述符函數(shù)接口的抽象方法的前面基本上就是Lambda表達(dá)式的簽名。我們將這種抽象方法叫做函數(shù)描述符。例如,Runnable接口可以看作一個(gè)什么也不接受什么也不返回的函數(shù)簽名,因?yàn)樗挥幸粋€(gè)叫做run的抽象方法,這個(gè)方法沒有參數(shù)并且是無返回的。
使用函數(shù)式接口函數(shù)式接口很有用,因?yàn)槌橄蠓椒ǖ暮灻梢悦枋鯨ambda表達(dá)式的簽名。函數(shù)式接口的抽象方法的簽名稱為函數(shù)描述符。
Predicate在第一章的讀書筆記中,有提到過Predicate這個(gè)接口,現(xiàn)在我們來詳細(xì)的了解一下它。
java.util.function.Predicate
@FunctionalInterface public interface Predicate{ boolean test(T t); } private static List filter(List list, Predicate predicate) { List result = new ArrayList<>(); for (T t : list) { if (predicate.test(t)) { result.add(t); } } return result; } List strings = Arrays.asList("Hello", "", "Java8", "", "In", "Action"); Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List stringList = filter(strings, nonEmptyStringPredicate); // [Hello, Java8, In, Action] System.out.println(stringList);
如果,你去查看Predicate這個(gè)接口的源碼你會發(fā)現(xiàn)有一些and或者or等等一些其他的方法,并且這個(gè)方法還有方法體,不過你目前無需關(guān)注這樣的方法,以后的文章將會介紹到為什么在接口中能定義有方法體的方法。
Consumerjava.util.function.Consumer
@FunctionalInterface public interface ConsumerFunction{ void accept(T t); } private static void forEach(List list, Consumer consumer) { for (T i : list) { consumer.accept(i); } } // 使用Consumer forEach(Arrays.asList("Object", "Not", "Found"), (String str) -> System.out.println(str)); forEach(Arrays.asList(1, 2, 3, 4, 5, 6), System.out::println);
java.util.function.Function
@FunctionalInterface public interface Function{ R apply(T t); } private static List map(List list, Function function) { List result = new ArrayList<>(); for (T s : list) { result.add(function.apply(s)); } return result; } List map = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length()); // [7, 2, 6] System.out.println(map);
我們剛剛了解了三個(gè)泛型函數(shù)式接口:Predicate
回顧一下:Java類型要么用引用類型(比如:Byte、Integer、Object、List),要么是原始類型(比如:int、double、byte、char)。但是泛型(比如Consumer
Listlist = new ArrayList<>; for (int i = 0; i < 100; i++) { list.add(i); }
但是像這種自動(dòng)裝箱和拆箱的操作,性能方面是要付出一些代價(jià)的。裝箱的本質(zhì)就是將原來的原始類型包起來,并保存在堆里。因此,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取被包裹的原始值。
Java8為我們前面所說的函數(shù)式接口帶來了一個(gè)專門的版本,以便在輸入和輸出都是原始類型時(shí),避免自動(dòng)裝箱的操作。比如,在下面的代碼中,使用IntPredicate就避免了對值1000進(jìn)行裝箱操作,但要使用Predicate
@FunctionalInterface public interface IntPredicate { boolean test(int value); } IntPredicate evenNumbers = (int i) -> i % 2 == 0; // 無裝箱 evenNumbers.test(1000); PredicateoddNumbers = (Integer i) -> i % 2 == 1; // 裝箱 oddNumbers.test(1000);
一般來說,針對專門的輸入?yún)?shù)類型的函數(shù)式接口的名稱都要加上對應(yīng)的原始類型前綴,比如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function接口還有針對輸出參數(shù)類型變種:ToIntFunction
Java8中還有很多常用的函數(shù)式接口,如果你有興趣可以去查找一些相關(guān)的資料,了解了這些常用的函數(shù)接口之后,會對你以后了解Stream的知識有很大的幫助。
《Java8實(shí)戰(zhàn)》這本書第三章的內(nèi)容很多,所以我打算分兩篇文章來寫。這些讀書筆記系列的文章內(nèi)容很多地方都是借鑒書中的內(nèi)容。如果您有時(shí)間、興趣和經(jīng)濟(jì)的話可以去買這本書籍。這本書我看了兩遍,是一本很不錯(cuò)的技術(shù)書籍。如果,您沒有太多的時(shí)間那么您就可以關(guān)注我的微信公眾號或者當(dāng)前的技術(shù)社區(qū)的賬號,利用空閑的時(shí)間看看我的文章,非常感謝您對我的關(guān)注!
代碼示例:Github:chap3
Gitee: chap3
公眾號文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/76688.html
摘要:上下文比如,接受它傳遞的方法的參數(shù),或者接受它的值得局部變量中表達(dá)式需要類型稱為目標(biāo)類型。但局部變量必須顯示的聲明,或?qū)嶋H上就算。換句話說,表達(dá)式只能捕獲指派給它們的局部變量一次。注捕獲實(shí)例變量可以被看作捕獲最終局部變量。 由于第三章的內(nèi)容比較多,而且為了讓大家更好的了解Lambda表達(dá)式的使用,也寫了一些相關(guān)的實(shí)例,可以在Github或者碼云上拉取讀書筆記的代碼進(jìn)行參考。 類型檢查、...
摘要:依舊使用剛剛對蘋果排序的代碼。現(xiàn)在,要做的是篩選出所有的綠蘋果,也許你會這一個(gè)這樣的方法在之前,基本上都是這樣寫的,看起來也沒什么毛病。但是,現(xiàn)在又要篩選一下重量超過克的蘋果。 《Java8實(shí)戰(zhàn)》-讀書筆記第一章(01) 最近一直想寫點(diǎn)什么東西,卻不知該怎么寫,所以就寫寫關(guān)于看《Java8實(shí)戰(zhàn)》的筆記吧。 第一章內(nèi)容較多,因此打算分幾篇文章來寫。 為什么要關(guān)心Java8 自1996年J...
摘要:但是到了第二天,他突然告訴你其實(shí)我還想找出所有重量超過克的蘋果。現(xiàn)在,農(nóng)民要求需要篩選紅蘋果。那么,我們就可以根據(jù)條件創(chuàng)建一個(gè)類并且實(shí)現(xiàn)通過謂詞篩選紅蘋果并且是重蘋果酷,現(xiàn)在方法的行為已經(jīng)取決于通過對象來實(shí)現(xiàn)了。 通過行為參數(shù)化傳遞代碼 行為參數(shù)化 在《Java8實(shí)戰(zhàn)》第二章主要介紹的是通過行為參數(shù)化傳遞代碼,那么就來了解一下什么是行為參數(shù)化吧。 在軟件工程中,一個(gè)從所周知的問題就是,...
摘要:收集器用作高級歸約剛剛的結(jié)論又引出了優(yōu)秀的函數(shù)式設(shè)計(jì)的另一個(gè)好處更易復(fù)合和重用。更具體地說,對流調(diào)用方法將對流中的元素觸發(fā)一個(gè)歸約操作由來參數(shù)化。另一個(gè)常見的返回單個(gè)值的歸約操作是對流中對象的一個(gè)數(shù)值字段求和。 用流收集數(shù)據(jù) 我們在前一章中學(xué)到,流可以用類似于數(shù)據(jù)庫的操作幫助你處理集合。你可以把Java 8的流看作花哨又懶惰的數(shù)據(jù)集迭代器。它們支持兩種類型的操作:中間操作(如 filt...
摘要:內(nèi)部迭代與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進(jìn)行的。流只能遍歷一次請注意,和迭代器類似,流只能遍歷一次。 流(Stream) 流是什么 流是Java API的新成員,它允許你以聲明性方式處理數(shù)據(jù)集合(通過查詢語句來表達(dá),而不是臨時(shí)編寫一個(gè)實(shí)現(xiàn))。就現(xiàn)在來說,你可以把它們看成遍歷數(shù)據(jù)集的高級迭代器。此外,流還可以透明地并行處理,你無需寫任何多線程代碼了!我會在后面的筆記中...
閱讀 2561·2021-11-22 09:34
閱讀 3539·2021-11-15 11:37
閱讀 2341·2021-09-13 10:37
閱讀 2105·2021-09-04 16:40
閱讀 1564·2021-09-02 15:40
閱讀 2456·2019-08-30 13:14
閱讀 3326·2019-08-29 13:42
閱讀 1903·2019-08-29 13:02