什麼是Lambda?java
能夠把Lambda表達式理解爲 簡潔的表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、函數主體、返回類型,可能還有一個能夠拋出的異常列表。程序員
使用Lambda可讓你更積極的使用行爲參數化,而不用像匿名類那樣寫不少模板代碼。數據庫
Lambda表達式由三部分組成:express
(Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
參數列表:這裏採用了Comparator中Compare方法的兩個參數,兩個 Apple。編程
箭頭:-> 把參數和函數主體分開。c#
函數主體:比較兩個Apple的重量,表達式就是Lambda的返回值了。app
Java 8中有效的Lambda表達式ide
//具備一個String類型的參數 返回一個int Lambda沒有return 語句,由於已經隱含了 (String s) -> s.length() //Apple類型的參數 返回一個boolean (Apple a) -> a.getWeight() > 150 //兩個int參數 沒有返回值 (int x, int y) ->{ System.out.println(x+y); } //空參數 返回int 42 () -> 42
Java語言設計者選擇這樣的語法,是由於在C#和Sala等語言中相似的功能廣受歡迎。Lambda的基本語法是:函數
(parameters) -> expression //或者使用花括號 (parameters) -> { statements; }
注意: 在沒有花括號時,是不須要return的。在有花括號寫多行時,是須要有return的。性能
在哪裏以及如何使用Lambda
你能夠在函數式接口上使用Lambda表達式,昨天的例子中filter方法中的參數 Predicate<T> 就是一個函數式接口。
List<Apple> greenApple = filter(apples,(Apple apple) -> "green".equals(apple.getColor()));
函數式接口
函數式接口就是之定義一個抽象方法的接口。如昨天定義的Predicate<T>,它就是一個函數式接口,由於它僅僅定義了一個抽象方法:
public interface Predicate<T> { boolean test(T t); }
昨天還使用了兩個Java API中的函數式接口,就是Comparator和Runnable
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); } @FunctionalInterface public interface Runnable { public abstract void run(); }
@FunctionalInterface註解:標記該接口會涉及成一個函數式接口,若是你用這個註解標記了一個接口,它只能定義一個抽象方法,若是多個編譯器會報錯。這個註解不是必須的,只是爲了更好的閱讀,和@Override註解同樣(表示該方法是一個重寫方法)。
Lambda表達式容許你直接之內聯的形式爲函數式接口的抽象方法提供實現,並把整個表達式做爲函數式接口的實例(具體來講,是函數式接口一個具體實現的實例)。使用匿名內部類也能夠完成一樣的事情,只不過比較笨拙。
函數描述符
函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。咱們將這種抽象方法叫作函數描述符。例如:Runnable接口能夠看作一個什麼也不接受什麼也不返回的函數簽名,由於它只有一個void run的抽象方法。 Lambda中 () -> void 表明了參數列表爲空,而且什麼也不返回,這正是Runnable接口所表明的。
由此可知:Lambda表達式能夠被賦給一個變量,或傳遞給一個接受函數式接口做爲參數的方法。固然這個Lambda表達式的簽名要和函數式接口的抽象方法同樣。
public static void process(Runnable r){ r.run(); }
直接把一個Lambda表達式傳遞給process方法
process(() -> System.out.println("hehe"));
爲何只有在須要函數式接口的時候才能夠傳遞Lambda呢?
語言設計者也考慮過其餘方法,例如給Java添加函數類型,可是他們選擇瞭如今這種方式,由於這種方式天然且能避免語言變得更復雜。
環繞執行模式
經過一個例子,在實踐中利用Lambda和行爲參數化來讓代碼更爲簡潔更爲靈活。在資源處理(例如處理文件或數據庫)時一個常見的模式就是打開一個資源,作一些處理,而後關閉資源。這個設置和清理階段老是很相似,而且會圍繞着執行處理的那些重要代碼。這就是所謂的環繞執行(excute around)模式。例如:從一個文件中讀取一行所需的末班代碼(使用了Java 7中的帶資源的try語句,它已經簡化了代碼,所以不須要顯示的關閉資源了相似c#中的using)。
public static String processFile() throws IOException { try(BufferedReader br = new BufferedReader(new FileReader("/Users/baidawei/Desktop/test.txt"))) { return br.readLine(); } }
第1步:記得行爲參數化
如今這段代碼是有侷限性的,你只能讀取文件的第一行,若是你想返回兩行等操做時,理想的狀況下是要重用執行設置和清理的代碼,並告訴processFile方法對文件執行不一樣的操做。你須要一種方法把行爲傳遞給processFile,以便它能夠利用BufferedReader執行不一樣的行爲。
你須要一個接受BufferedReader並返回String的Lambda,例如打印兩行以下寫法:
System.out.println(processFile((BufferedReader br) -> br.readLine() + br.readLine()));
第2步:使用函數式接口來傳遞行爲
由於Lambda僅可用於上下文是函數式接口的狀況,因此你須要建立一個能匹配BufferedReader->String,還能夠拋出IOException異常的接口,首先定義這個接口就命名爲BufferedReaderProcessor吧
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
第3步:執行一個行爲
任何BufferedReader->String形式的Lambda均可以做爲參數來傳遞,由於他們符合BufferedReaderProcessor接口的process方法的簽名。如今只須要一種方法在processFile主體內執行Lambda所表明的代碼。
public static String processFile(BufferedReaderProcessor p) throws IOException { try(BufferedReader br = new BufferedReader(new FileReader("/Users/baidawei/Desktop/test.txt"))){ //處理BufferedReader對象 return p.process(br); } }
第4步:傳遞Lambda
Lambda表達式容許直接內聯,爲函數式接口的抽象方法提供實現,而且將整個表達式做爲函數式接口的一個實例。如今就能夠經過傳遞不一樣的Lambda重用processFile方法了
處理一行:
System.out.println(processFile((BufferedReader br) -> br.readLine()));
處理兩行:
System.out.println(processFile((BufferedReader br) -> br.readLine() + br.readLine()));
咱們已經展現瞭如何利用函數式接口來傳遞Lambda,但你仍是得定義你本身的接口,在下面將展現在Java 8 中加入的新接口,你能夠重用它來傳遞多個不一樣的Lambda。
使用函數式接口
前面已經說過,函數式接口只定義一個抽象方法。函數式接口頗有用,由於抽象方法的簽名能夠描述Lambda表達式的簽名。函數式接口的抽象方法的簽名稱爲函數描述符。因此爲了應用不一樣的Lambda表達式,Java 8庫設計師幫你在java.util.function包中引入了幾個新的函數式接口。
Predicate
java.util.function.Predicate<T>接口定義了一個test的抽象方法,它接受泛型T對象,並返回一個boolean。這偏偏和你以前建立的同樣,如今就能夠直接使用了。在你須要表示一個設計類型T的布爾表達式時,就可使用這個接口。該接口定義以下:
package java.util.function; @FunctionalInterface public interface Predicate<T> { boolean test(T t); }
你能夠定義一個接受String對象的Lambda表達式。
//定義過濾方法 接受一個Predicate<T> public static <T> List<T> filter(List<T> list, Predicate<T> p) { List<T> result = new ArrayList<>(); for(T e : list){ if(p.test(e)){ result.add(e); } } return result; } //去掉空字符串 public static void main(String[] args){ Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty(); List<String> nonEmpty = filter(Arrays.asList("sf","","sdf",""),nonEmptyStringPredicate); System.out.println(nonEmpty); }
Consumer
java.util.function.Consumer<T>定義了一個名叫accept的抽象方法,它接受泛型T的對象,沒有返回值。你須要訪問類型T的對象,執行某型操做,就可使用這個接口。該接口定義以下:
package java.util.function; @FunctionalInterface public interface Consumer<T> { void accept(T t); }
使用它來建立一個forEach方法,接受一個Integers列表,並對其每一個元素執行操做。
//建立forEach方法 沒有返回值 public static <T> void forEach(List<T> list, Consumer<T> c){ for(T i : list){ c.accept(i); } } public static void main(String[] args) { forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i)); }
Lambda是Consumer中accept方法的實現
Function
java.util.function.Function<T,R>接口定義了一個叫作apply的方法,它接受一個泛型T的對象,並返回一個泛型R的對象。若是你須要定義一個Lambda,將輸入對象的信息映射到輸出,就可使用這個接口。該接口的定義以下:
package java.util.function; @FunctionalInterface public interface Function<T, R> { R apply(T t); }
好比將List字符串映射爲一個 字符串長度的Integer List
public static <T,R> List<R> map(List<T> list, Function<T,R> f){ List<R> result= new ArrayList<>(); for(T s: list){ result.add(f.apply(s)); } return result; } public static void main(String[] args) { List<Integer> l = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length()); System.out.println(l); }
原始類型特化
在Java中要麼是引用類型(Byte、Integer、Object、List),要麼是原始類型(int、double、byte、char)。可是泛型(好比Consumer<T>中的T)只能綁定到引用類型。這是由泛型內部的實現方式形成的。所以,在Java中有一個將原始類型轉換爲對應的引用類型的機制。稱爲裝箱(boxing)。相反的操做,也就是將引用類型轉換爲對應的原始類型,稱爲拆箱(unboxing)。Java還有一個自動裝箱機制來幫助程序員執行這一任務:裝箱和拆箱是自動完成的。好比,這就是爲何下面的代碼是有效的(一個int被裝箱稱爲Integer):
List<Integer> list = new ArrayList<>(); for (int i = 1;i<100;i++){ list.add(i); }
可是這在性能方面是要付出代價的。裝箱後的值本質上就是把原始類型包裹起來,並保存在堆裏。所以,裝箱後須要更多的內存,並須要額外的內存搜索來獲取被包裹的原始值。
Java 8爲咱們前面所說的函數式接口帶來了一個專門的版本,以便在輸入和輸出都是原始類型時避免自動裝箱的操做。好比,在下面的代碼中使用IntPredicate就避免了對值1000進行裝箱操做,但要是用Predicate<integer>就會把參數1000裝箱到一個Integer對象中:
package java.util.function; @FunctionalInterface public interface IntPredicate { boolean test(int value); }
//避免自動裝箱 IntPredicate evenNumbers = (int i) -> i % 2 == 0; evenNumbers.test(1000); //會形成裝箱 Predicate<Integer> evenNumbers2 = (Integer i) -> i % 2 == 0; evenNumbers2.test(1000);
通常來講,針對專門的輸入參數類型的函數式接口名稱都要加上對應的原始前綴,好比DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function接口還有針對輸出類型參數的變種:ToIntFunction<T>、IntToDoubleFunction等。
下表中總結了Java API中提供的最經常使用的函數式接口及函數描述符。若是有須要能夠本身設計一個! (T,U) -> R的表達方式應當如何思考一個函數描述符。表的左側表明了參數類型。這裏它表明一個函數,具備兩個參數,分別爲泛型T和U,返回類型爲R。
函數式接口 | 函數描述符 | 原始類型特化 |
Predicate<T> | T->boolean | IntPredicate、LongPredicate、DoublePredicate |
Consumer<T> | T->void | IntConsumer、LongConsumer、DoubleConsumer |
Function<T,R> | T->R | IntFunction<R>、IntToDoubleFunction、IntToLongFunction LongFunction<R>、LongToDoubleFunction、LongToIntFunction DoubleFunction<R>、ToIntFunction<T>、ToDoubleFunction<T>、ToLongFunction<T> |
Supplier<T> | ()->T | BooleanSupplier、IntSupplier、LongSupplier、DoubleSupplier |
UnaryOperator<T> | T->T | IntUnaryOperator、LongUnaryOperator、DoubleUnaryOpertor |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator、LongBinaryOperator、DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer<T>、ObjLongConsumer<T>、ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>、ToLongBiFunction<T,U>、ToDoubleBiFunction<T,U> |
這些函數式接口,能夠用於描述各類Lambda表達式的簽名了。
使用案例 | Lambda的例子 | 對應的函數式接口 |
布爾表達式 | (List<String> list) -> list.isEmpty() | Predicate<List<String>> |
建立對象 | () -> new Apple(10) | Supplier<Apple> |
消費一個對象 | (Apple a) -> System.out.println(a.getWeight()) | Consumer<Apple> |
從一個對象中選擇/提取 | (String s) -> s.length() | Function<Stirng,Integer>或ToIntFunction<String> |
合併兩個值 | (int a,int b) - > a * b | INtBinaryOperator |
比較兩個對象 | (Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) | Comparator<Apple>或Bifunction<Apple,Apple,Integer> 或ToIntBiFunction<Apple,Apple> |
關於異常
請注意,任何的函數式接口都不容許拋出受檢異常(checked exception)。若是你須要Lambda表達式來拋出異常,有兩種辦法:一是將lambda表達式包含在try catch塊中,另外一種方式就是本身定義函數式接口,並聲明受檢異常,像剛剛定義的BufferedReaderProcessor接口
類型檢查
Lambda的類型是從使用Lambda的上下文種推斷出來的。上下文(好比,接受它傳遞的方法的參數,或接受它的值的局部變量)中Lambda表達式須要的類型稱爲目標類型。
一樣的Lambda,不一樣的函數式接口
有了目標類型的概念,同一個Lambda表達式就能夠與不一樣的函數式接口聯繫起來,只要他們的抽象方法簽名可以兼容。好比,前面提到的Callabel和PrivilegedAction這兩個接口都表明着什麼也不接受且返回一個泛型T的函數,所以,下面兩個賦值都是有效的:
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
還有比較蘋果重量時,也是可使用不一樣的函數式接口的
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); ToIntBiFunction<Apple,Apple> c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); BiFunction<Apple,Apple,Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
菱形運算符<>
在Java 7中引入了菱形運算符<> ,利用泛型推斷從上下文推斷類型的思想。
List<String> strs = new ArrayList<>(); List<Apple> apples = new ArrayList<>();
特殊的void兼容規則
若是一個Lambda的主體是一個語句表達式,它就和一個返回void的函數描述符兼容(固然須要參數列表也兼容)如:
//Predicate 返回一個boolean Predicate<String> s1 = s -> strs.add(s); //Consumer 返回一個void Consumer<String> s2 = s -> strs.add(s);
類型推斷
由於Java編譯器能夠從上下文(目標類型)推斷出用什麼函數式接口,因此它也能夠推斷出適合Lambda的簽名,由於函數描述符能夠經過目標類型來獲得。換句話說,Java編譯器會像下面這樣推斷出Lambda的參數類型:
//此處a 沒有顯示類型 當只有一個參數時 兩邊的()能夠省略 List<Apple> greenApples = filter(apples,a-> "green".equals(a.getColor())); //a1 a2 推斷出apple Comparator<Apple> c = (a1,a2) -> a1.getWeight().compareTo(a2.getWeight());
這種方式有時易讀,有時不易讀。
使用局部變量
Lambda也支持使用自由變量(不是參數,而是在外層做用域中定義的變量),就像匿名類同樣。它們被稱做捕獲Lambda。例如,下面的Lambda捕獲了portNumber變量。
int portNumber = 8080; Runnable r = () -> System.out.println(portNumber);
Lambda雖然能夠沒有限制的捕獲實例變量和靜態變量。但局部變量必須聲明爲final。換句話說,Lambda表達式只能捕獲指派給它們局部變量一次。例以下面這個就沒法編譯:
int portNumber = 8080; Runnable r = () -> System.out.println(portNumber); portNumber = 333;
由於Lambda捕獲的局部變量必須是final , 而這裏又對portNumber進行了賦值 因此會編譯報錯!
對局部變量的限制
爲何局部變量有這些限制?第一,實例變量和局部變量背後的實現有一個關鍵不一樣。實例變量都存儲在堆中,而局部變量保存在棧上。若是Lambda能夠直接訪問局部變量,而Lambda是在一個線程中使用的,則使用Lambda的線程。可能會在分配該變量的線程將這個變量收回以後,去訪問該變量。所以,Java在訪問自由局部變量時,其實是在訪問它的副本,而不是訪問原始變量。若是局部變量僅僅賦值一次那就沒有什麼區別了-----所以就有了這個限制。
第二,這一限制不鼓勵你使用改變外部變量的典型命令式編程模式(這種模式會阻礙很容易作到的並行處理)。
能夠編譯成功的,實例變量以下:
Apple a = new Apple(); Runnable r = () -> System.out.println(a.getWeight()); a.setWeight(10.1); r.run();
運行後輸出10.1
方法引用
方法引用讓你能夠重複使用現有的方法定義,並像Lambda同樣傳遞他們。在一些狀況下,他們比使用Lambda更易讀,以下 一個排序的例子:
//使用Lambda apples.sort((a1,a2)->a1.getWeight().compareTo(a2.getWeight())); //使用方法引用 apples.sort(comparing(Apple::getWeight));
方法引用能夠被看作僅僅調用特定方法的Lambda的一種快捷寫法。它的思想是,若是一個Lambda表明的是「直接調用這個方法」,那最好仍是用名稱來調用它,而不是去描述如何調用它。
事實上,方法引用就是讓你根據已有的方法實現來建立Lambda表達式。當你須要方法引用時,目標引用放在::前,方法放在::後,如Apple::getWeight就是引用了Apple類中定義的方法getWeight。不須要括號,由於沒有實際調用這個方法,只是引用。至關於lambda: (Apple a) -> a.getWeight()的快捷寫法。
如何構建方法引用
1.靜態方法 (如:Integer的parseInt方法,寫做Integer::parseInt).
2.現有對象的實例方法 (如:假設有一個Apple類型的apple變量,就能夠 apple::getWeight).
3.任意類型實例方法 (如:String的length方法,String::length).
當方法引用一個對象的時候,而這個對象自己是Lambda的一個參數,例如 (String s)->s.toUpperCase() 就能夠寫做String::toUpperCase。
舉個栗子:對一個字符串List排序,忽略大小寫。
List<String> atr = Arrays.asList("a","c","D","E"); atr.sort((s1,s2)->s1.compareToIgnoreCase(s2)); //使用方法引用 atr.sort(String::compareToIgnoreCase);
編譯器會進行一種與Lambda相似的類型檢查過程,來肯定給定的函數式接口,這個方法引用是否有效:方法引用必須和上下文類型匹配。
構造函數引用
對於一個現有的構造函數,能夠利用它的名稱和關鍵字new來建立愛你它的一個引用:ClassName::new。它的功能與指向靜態方法的引用相似。
1. 沒有參數:假設你有一個空餐構造函數,它就適合Supplier的簽名() -> Apple。
//無參構造函數 Supplier<Apple> c1 = Apple::new; //調用Supplier的get方法產生一個新的Apple Apple a1 = c1.get(); //等價於Lambda Supplier<Apple> c1 = () -> new Apple(); Apple a1 = c1.get();
2. 1個參數:假設你有一個Apple(Double weight)的簽名,那麼它就適合Function接口的簽名.
//指向 Apple(Double Weight) Function<Double,Apple> c2 = Apple::new; //調用Function的apply傳入重量 獲取一個Apple對象 Apple a2 = c2.apply(18d); //等價於 Function<Double,Apple> c2 = (weight) -> new Apple(weight); Apple a2 = c2.apply(18d);
再來看看之間建立的map方法,咱們此次傳遞Apple的構造函數,就獲得了一個具備不一樣重量的蘋果的List:
public static void main(String[] args) { List<Apple> apps = map(Arrays.asList(1d,2d,3d), Apple::new); System.out.println(apps); } public static <T,R> List<R> map(List<T> list, Function<T,R> f){ List<R> result= new ArrayList<>(); for(T s: list){ result.add(f.apply(s)); } return result; } //輸出結果 [Apple{Id=null, Color='null', Weight='1.0'}, Apple{Id=null, Color='null', Weight='2.0'}, Apple{Id=null, Color='null', Weight='3.0'}]
3. 2個參數假設 你有一個具備兩個參數的構造方法Apple(String Color,Double Weight) 那麼它就適合BiFunction接口的簽名:
//指向 Apple(String Color,Double Weight) BiFunction<String,Double,Apple> c3 = Apple::new; //調用BiFunction函數的apply方法,給出顏色和重量 產生一個Apple對象 Apple a3 = c3.apply("green",33d); //至關於 BiFunction<String,Double,Apple> c3 = (color,weight) -> new Apple(color,weight); Apple a3 = c3.apply("green",33d);
4. 3個參數 若是超過2個 那麼沒有默認定義好的了,須要咱們本身建立與構造函數引用的簽名匹配的函數式接口。如:
public interface TriFunction<T,U,V,R>{ R apply(T t,U u,V v); } //如今就能夠這樣使用了 TriFunction<Integer,Integer,Integer,Color> colorFactory = Color::new;
複合Lambda表達式
能夠把多個簡單的Lambda複合成複雜的表達式。在Predicate等函數式接口中提供了默認方法(Java 8新出的 一種能夠在接口中寫方法體的方法),這些接口提供了不少有用的複合方法。
1.比較器複合
//對Apple按照重量排序 Comparator<Apple> c = Comparator.comparing(Apple::getWeight); //逆序 接口提供了一個reversed()方法 Comparator<Apple> c1 = Comparator.comparing(Apple::getWeight).reversed(); //比較器鏈 thenby Comparator<Apple> c2 = Comparator.comparing(Apple::getWeight) .reversed() .thenComparing(Apple::getColor);
2.謂詞複合
謂詞接口包含三個方法:negate、and和or。讓你能夠重用已有的Predicate來建立更復雜的謂詞。
Predicate<Apple> redApple = (Apple a) -> a.getColor().equals("red"); //取反 Predicate<Apple> notRedApple = redApple.negate(); //而且 Predicate<Apple> redAndBigApple = redApple.and(a->a.getWeight()>32); //或者 Predicate<Apple> redOrGreenApple = redApple.or(a->a.getColor().equals("green"));
函數複合
最後,你還能夠把Function接口所表明的Lambda表達式複合起來。Function接口爲此配了andThen和compose兩個默認方法,他們都會返回Function的一個實例。
andThen方法會返回一個函數,它先對輸入應用一個給定函數,在對輸出應用另外一個函數。好比函數f=(x->x+1),另外一個函數g給數字乘2,你能夠將他們組合成一個函數。
Function<Integer,Integer> f = x-> x+1; Function<Integer,Integer> g = x -> x * 2; Function<Integer,Integer> h = f.andThen(g); int result = h.apply(1); //4
compose方法,先把給定的函數用做compose的參數裏面給的那個函數,而後再把函數自己用於結果,好比andThen至關於f(g(x)),compose至關於g(f(x))。
Function<Integer,Integer> f = x-> x+1; Function<Integer,Integer> g = x -> x * 2; Function<Integer,Integer> h = f.compose(g); int result = h.apply(1); //3
小結:
1.Lambda表達式能夠理解爲一種匿名函數:它沒有名稱,但有參數列表、函數主體、返回類型,還能夠有一個拋出異常的列表。
2.Lambda表達式讓你能夠寫出更簡潔的代碼。
3.函數式接口就是僅僅聲明瞭一個抽象方法的接口。
4.只有在接受函數式接口的地方纔可使用Lambda表達式。
5.Lambda表達式容許你直接內聯,爲函數式接口的抽象方法提供實現,而且將整個表達式做爲函數式接口的一個實例。
6.Java 8自帶一些經常使用的函數式接口,放在java.util.function包裏,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>。
7.爲了不裝箱操做,對Predicate<T>和Function<T,R>等通用函數式接口的原始類型特化:IntPredicate、IntToLongFunction等
8.環繞執行模式(即在方法鎖必須的代碼中間,你須要執行點什麼操做,好比資源分配和清理)能夠配合Lambda提升靈活性和可重用性。
9.Lambda表達式所須要表明的類型稱爲目標類型。
10.方法引用讓你重複使用現有的方法實現並直接傳遞它們。
11.Comparator、Predicate和Function等函數式接口都有幾個能夠用來結合Lambda表達式的默認方法。