Lambda表達式


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);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM