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