9、Lambda表達式
java是強類型語言,必須指定類型
(String first, String second)->first.length()-second.length();
如果lambda表達式的表達體執行一個無法用一個表達式表示的計算,那么用{}包裹代碼並明確些上return語句。
1 (String first, String second) -> {
2 int difference = first.length() < second.length(); 3 if(difference <0) return -1; 4 else if(difference >0) return 1; 5 else return 0; 6 }
如果lambda表達式沒有參數,則寫一個空的小括號。
1 Runnable task = () -> { for(int i=0;i<1000;i++) doWork();}
如果lambda表達式的參數類型可以被對到出來,則可以省略類型。
Comparator<String> comp = (first, second) -> first.length() - second.lenght();
這里因為lambda表達式被賦值給String比較器,編譯器可以推斷出類型。如果某方法只有一個參數,且參數類型可推導,甚至可以省略小括號。
1 EventHandler<ActionEvent> listener = event -> System.out.println();
永遠不要為lambda表達式指定返回類型,編譯器會從lambda表達式推斷出類型,並檢查返回類型是否與期望的類型匹配。
10、函數式接口
無論何時,當你期望只有一個抽象方法的接口對象時,就可以提供一個lambda表達式。這樣的接口被稱為函數式接口
Arrays.sort方法。該方法的第二個參數要求一個Comparator接口的實例。直接提供一個lambda:
1 Array.sort(words,(first, second) -> first.length()-second.length());
表達式背后,Arrays.sort方法的第二個參數變量接受一個實現了Comparator<String>接口的類的實例。調用該對象的compare方法會執行lambda表達式中的代碼。這些對象和類的管理完全依賴於實現,並是高度優化的。
在Java中,lambda表達式只能將其放入類型為函數式接口的變量中,這樣他就被轉換為該接口的實例。
不能將lambda表達式復制給Object的變量,因為Object是類,不是函數式接口。
11、方法引用
當傳遞給其他代碼的操作已經有實現的方法了,可以使用方法引用
1 Arrays.sort(Strings, (x,y) -> x.compareToIgnoreCase(y));
2 //也可以傳入方法表達式:
3 Arrays.sort(strings, String::compareToIgnoreCase);
Object定義了inNull方法,返回x==null的值。
list.removeIf(Object::isNull);
1 list.forEach(x-> System.out.println(x));
2 //可以寫成
3 list.forEach(System.out::println);
操作符::將方法名與類或對象分隔開
1、類::實例方法 第一個參數變成方法的接受者,其他的參數也傳遞給該方法。
String::compareToIgnoreCase 等同於 (x,y)->x.compareToIgnoreCase(y)
2、類::靜態方法 所有的參數傳遞給靜態方法。
Objects::isNull 等同於 x->Objects.isNull(x)
3、對象::實例方法 在給定的對象上調用方法,並且參數傳遞給實例方法。
System.out::println 等同於 x->System.out.println(x)
在內部類中,可以用"外部類.this::方法名稱"捕獲外部類的this引用。
使用數組構造函數可以繞過java的限制,構造一個泛型數組。
Employee[] buttons = stream.toArray(Employee[]::new)
12、使用lambda表達式
1、實現延遲執行
使用lambda表達式就在於延遲執行。如果想立即執行一段代碼,無需將代碼封裝進lambda,可以直接調用。
延遲執行的原因:(其實就是支持函數式變成的接口有哪些)
在另一個單獨的線程中運行代碼
多次運行代碼
在算法的恰當時刻運行代碼(排序中的比較操作)
當某些情況發生時運行代碼(按鈕被點擊,數據到達)
只有在需要時才運行的代碼
如:想將一個行為重復10次
repeat(10,()->System.out.println("a"));
//要選擇一個函數式接口來接受lambda表達式
public static void repeat(int n, Runnalbe action){ for(int i=0;i<n;i++) action.run(); } //當action.run()被調用時,lambda表達式體被執行。
2、常用的函數式接口
大多數標准的函數式接口都有用來產生或者組裝函數的非抽象方法。Predicate.isEqual(a)與a::equals相同
表3-2 為原始類型提供的函數式接口:p、q 為int、long、double 類型,P、Q 為Int、Long、Double 類型
比如可以用IntConsumer代替Consumer<Integer>
3、實現自己的函數式接口
如果想用顏色填充一張圖片,用戶可以提供為每個像素產生顏色的函數,標准類型中沒有(int,int)->color。這時可以用BiFunction<Integer,Integer,Color>但是這樣會自動裝箱。
或者定義一個新的接口:
1 @FunctionalInterface
2 public interface PixelFunction{ 3 Color apply(int x, int y) 4 }
用@FunctionalInterface注解標記函數式接口,這樣編譯器會檢查出被注釋的實體是一個帶有單個抽象方法的接口。JavaDoc頁面也會有函數式接口的聲明。
實現如下:
1 BufferedImage createImage(int width, int height, PixelFunction f){
2 BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB); 3 for(int x =0;x<width;x++){ 4 for(int y =0;y<height;y++){ 5 Color color = f.apply(x,y)//這里f就是傳入的lambda表達式 6 image.setRGB(x,y,color.getRGB()); 7 } 8 retrun image; 9 } 10 } 11 12 //調用 13 BufferedImage frenchFlag = createImage(150,100, 14 (x,y)->x<50?Color.Blue:x<100?Color.WHITE:Color.Red);
13、lambda表達式的作用域
lambda表達式的方法體與嵌套代碼塊有相同的作用域。在lambda中不允許聲明一個與局部變量同名的參數或局部變量。
int first = 0; Comparator<String> comp =(first,second) ->first.length() - second.length(); //錯誤:變量first已經定義了
lambda中的this代表的是創建lambda表達式方法的this參數。
1 public class Application(){ 2 public void dowork(){ 3 Runnable runner = () ->{...;Sysout(this.toString);} 4 } 5 }
this.toString是調用Application對象的toString()方法,lambda的作用域被嵌套在dowork方法中。
14、訪問來自閉合作用域的變量
在lambda中訪問來自閉合方法或類的變量。
1 public static void repeatMessage(String text,int count){ 2 Runnable r = () -> { 3 for(int i =0;i<count;i++){ 4 System.out.println(text); 5 } 6 }; 7 new Thread(r).start(); 8 }
lambda訪問了定義在閉合域,而不是定義在表達式自身中的參數標量text
如果repeatMessage(“A”,1000)//在單獨的線程中將A打印1000次
如果lambda在repeatMessage返回之后很久才運行,此時參數變量已經消失了。
15、lambda表達式的理解
lambda表達式分為三個部分:1、代碼塊 2、參數 3、自由變量的值(既不是參數標量,也不是代碼內部定義的變量)
在repeatMessage中lambda由兩個自由變量,text和count。這些變量其實已經被lambda捕獲了(通過一個具體的實現細節來完成捕獲工作,將lambda轉變為帶有一個方法的對象,這樣自由變量的值就可以復制到對象的實例變量中)
閉包(closure)描述帶有自由變量值得代碼塊的技術,在java中lambda就是閉包
lambda只能引用哪些值不會改變的變量,只能捕獲變量的值,而不是變量的引用。
for(int i=0;i<n;i++){ new Thread(()->System.out.println(i)).start(); //錯誤,不能捕獲i }
lambda只能訪問來自閉合作用域的final局部變量。(同樣的規則適用於被局部內部類捕獲的變量)
注意:增強的for循環中的變量是有效final的,因為他的作用域是單個迭代。
for(String arg: args){ new Thread(()->System.out.println(arg)).start(); //可以捕獲 }
作為“有效final”規則的結果,lambda不能改變任何捕獲的變量。可以有效的防止多線程下的沖突。
禁止修改變量只是針對局部變量。如果count是實例變量或者外部類的靜態變量,及時並發時結果不確定,也不會有錯誤報告。
//可以用長度為1的數組繞過限制 int[] counter = new int[1]; button.setOnAction(event->counter[0]++); //counter是有效final的,一直引用同意個數組。但是是線程不安全的。
16、高階函數
處理貨返回函數的函數稱為高階函數。
1、返回函數的方法
假如想對字符串進行升序或降序排序。創建一個會產生正確比較器(comparator)的方法
1 public static Comparator<String> compareInDirection(int direction){ 2 return (x,y) -> direction * x.compareTo(y); 3 }
可以傳遞給另一個期望這種借口的方法
Arrays.sort(friends, comparaInDirection(-1));
2、修改函數的方法
1 list.sort(reverse(compare())); 2 3 public static Comparator<Integer> compare() { 4 return (x,y) -> x.compareTo(y); 5 } 6 7 public static Comparator<Integer> reverse(Comparator<Integer> comp){ 8 return (x,y) -> comp.compare(y, x); 9 }
3、Comparator
Comparator接口有很多有用的靜態方法,他們是產生comparator的高階函數。
comparing方法接受“key提取器”函數,將類型T映射到可比較的類型。函數可以引用到被比較的對象,並且是在返回的key上進行的比較。
1 Arrays.sort(people, Comparator.comparing(Person::getName)); 2 Arrays.sort(people, Comparator.comparing(Person::getName).thenComparing(Person::getFirstName)); 3 // 根據名字長度進行排序 4 Arrays.sort(people, Comparator.comparing(Person::getName),(s,t)->s.lenght-t.length()));
為了避免裝箱可以使用comparingInt
1 Arrays.sort(people,Comparator.comparingInt(p->p.getName().length()));
為了防止空指針異常,當有空對象時可以使用nullsFirst和nullsLast。使用naturalOrder和reserveOrder可以返回正序和逆序兩種比較器(自動推斷類型)。
1 Arrays.sort(people, comparing(Person::getMiddleName, nullsFirst(naturalOrder())));
17、局部內部類
在方法里定義的類為局部類。
//產生給定范圍的無限序列隨機整數 public static IntSequence randomInts(int low, int high) //因為IntSequence是個接口,所以必須返回實現該接口的某個類的對象 private static Random generator = new Random(); public static InSequence randomInts(int low, int high){ Class RandomSequence implements IntSequence{ public int next() {return low+generator.nextInt(high-low+1);} public boolean hasNext() {return true;} } return new RandomSequence(); }
局部類沒有聲明為public或private,因為對方法外部而言它是永遠不可訪問的。如果將RandomSequence轉變為嵌套類,將不得不提供一個顯示的構造函數,接受閉合作用域變量,但是用內部類就不用。
//現在可以使用lambda表達式 public static IntSequence randomInts(int low, int high){ return ()->low + generator.nextInt(high - low + 1); }