欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java8中Lambda表達式的理解與應(yīng)用

 更新時間:2022年02月16日 11:48:04   作者:湯圓  
Java8最值得學(xué)習(xí)的特性就是Lambda表達式和Stream?API,如果有python或者javascript的語言基礎(chǔ),對理解Lambda表達式有很大幫助,下面這篇文章主要給大家介紹了關(guān)于Java8中Lambda表達式的相關(guān)資料,需要的朋友可以參考下

簡介

Lambda表達式是一個可傳遞的代碼塊,可以在以后執(zhí)行一次或多次;

下面貼個對比代碼:

// Java8之前:舊的寫法
Runnable runnable = new Runnable() {
 @Override
 public void run() {
   System.out.println("old run");
}
};
Thread t = new Thread(runnable);

// Java8之后:新的寫法
Runnable runnable1 = ()->{
 System.out.println("lambda run");
};
Thread t1 = new Thread(runnable1);

可以看到,有了lambda,代碼變得簡潔多了

你可以把lambda當(dāng)作一個語法糖

下面讓我們一起來探索lambda的美好世界吧

正文

1. lambda的語法

下面分別說下語法中的三個組成部分

  • 參數(shù): ( Dog dog )

    • 參數(shù)類型可省略(當(dāng)編譯器可以自動推導(dǎo)時),比如Comparator<String> comparatorTest = (a, b)->a.length()-b.length();,可以推導(dǎo)出a,b都為String

    • 當(dāng)參數(shù)類型可省略,且只有一個參數(shù)時,括弧也可以省略(但是個人習(xí)慣保留)

  • 符號:->

  • 主體{ System.out.println("javalover"); }

    • 如果是一條語句,則需要加大括號和分號{;}(比如上圖所示)

    • 如果是一個表達式,則直接寫,啥也不加(比如a.length()- b.length()

2. 為啥引入lambda

為了簡化代碼

因為Java是面向?qū)ο笳Z言,所以在lambda出現(xiàn)之前,我們需要先構(gòu)造一個對象,然后在對象的方法中實現(xiàn)具體的內(nèi)容,再把構(gòu)造的對象傳遞給某個對象或方法

但是有了lambda以后,我們可以直接將代碼塊傳遞給對象或方法

現(xiàn)在再回頭看下開頭的例子

可以看到,用了lambda表達式后,少了很多模板代碼,只剩下一個代碼塊(最核心的部分)

3. 什么是函數(shù)式接口

就是只定義了一個抽象方法的接口

  • 正例:有多個默認方法,但是如果只有一個抽象方法,那它就是函數(shù)式接口,示例代碼如下

@FunctionalInterface
public?interface?FunctionInterfaceDemo?{
? ?void?abstractFun();
? ?default?void?fun1(){
? ? ? ?System.out.println("fun1"); ? ?
? }
? ?default?void?fun2(){
? ? ? ?System.out.println("fun2");
? } ?
}

這里的注解@FunctionalInterface可以省略,但是建議加上,就是為了告訴編譯器,這是一個函數(shù)式接口,此時如果該接口有多個抽象方法,那么編譯器就會報錯

  • 反例:比如A extends B,A和B各有一個抽象方法,那么A就不是函數(shù)式接口,示例代碼如下

// 編譯器會報錯,Multiple non-overriding abstract methods found in XXX
@FunctionalInterface
public?interface?NoFunctionInterfaceDemo?extends?FunctionInterfaceDemo{
?void?abstractFun2();
}

上面的父接口FunctionInterfaceDemo中已經(jīng)有了一個抽象方法,此時NoFunctionInterfaceDemo又定義了一個抽象方法,結(jié)果編譯器就提示了:存在多個抽象方法

在Java8之前,其實我們已經(jīng)接觸過函數(shù)式接口

比如Runnable 和 Comparable

只是沒有注解@FunctionalInterface。

那這個函數(shù)式接口要怎么用呢?

配合lambda食用,效果最佳(就是把lambda傳遞給函數(shù)式接口),示例代碼如下:

new?Thread(()?->?System.out.println("run")).start();

其中用到的函數(shù)式接口是Runnable

4. 什么是行為參數(shù)化

就是把行為定義成參數(shù),行為就是函數(shù)式接口

類似泛型中的類型參數(shù)化,類型參數(shù)化是把類型定義成參數(shù)

行為參數(shù)化,通俗點來說:

  • 就是用函數(shù)式接口形參

  • 然后傳入接口的各種實現(xiàn)內(nèi)容(即lambda表達式)作為實參

  • 最后在lambda內(nèi)實現(xiàn)各種行為(好像又回到多態(tài)的那一節(jié)了?這也是為啥多態(tài)是Java的三大特性的原因之一,應(yīng)用太廣泛了)

這樣來看的話,行為參數(shù)化和設(shè)計模式中的策略模式有點像了(后面章節(jié)會分別講常用的幾種設(shè)計模式)

下面我們手寫一個函數(shù)式接口來加深理解吧

5. 手寫一個函數(shù)式接口

下面我們循序漸進,先從簡單的需求開始

  • 第一步:比如我們想要讀取某個文件,那可以有如下方法:

public?static?String?processFile()?throws?IOException?{
? ?// Java7新增的語法,try(){},可自動關(guān)閉資源,減少了代碼的臃腫
? ?try(?BufferedReader?bufferedReader?=
? ? ? ?new?BufferedReader(new??FileReader("./test.txt"))){
? ? ? ?return?bufferedReader.readLine();
? }
}

可以看到,核心的行為動作就是 return bufferedReader.readLine();,表示讀取第一行的數(shù)據(jù)并返回

那如果我們想要讀取兩行呢?三行?

  • 第二步:這時就需要用到上面的函數(shù)式接口了,下面就是我們自己編寫的函數(shù)式接口

@FunctionalInterface
interface?FileReadInterface{
// 這里接受一個BufferedReader對象,返回一個String對象
? ?String?process(BufferedReader?reader)?throws?IOException;
}

可以看到,只有一個抽象方法process(),它就是用來處理第一步中的核心動作(讀取文件內(nèi)容)

至于想讀取多少內(nèi)容,那就需要我們在lambda表達式中定義了

  • 第三步:接下來我們定義多個lambda表達式,用來傳遞函數(shù)式接口,其中每個lambda表達式就代表了一種不同的行為,代碼如下:

// 讀取一行
FileReadInterface?fileReadInterface?=?reader?->?reader.readLine();
// 讀取兩行
FileReadInterface?fileReadInterface2?=?reader?->?reader.readLine()?+?reader.readLine();
  • 第四步:我們需要修改第一步的processFile(),讓其接受一個函數(shù)式接口,并調(diào)用其中的抽象方法,代碼如下:

// 參數(shù)為第二步我們自己手寫的函數(shù)式接口
public static String processFile(FileReadInterface fileReadInterface) throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
// 這里我們不再自己定義行為,而是交給函數(shù)式接口的抽象方法來處理,然后通過lambda表達式的傳入來實現(xiàn)多個行為
         return fileReadInterface.process(bufferedReader);
      }
  }
  • 第五步:拼接后,完整代碼如下:

public class FileReaderDemo {
   public static void main(String[] args) throws IOException {
// 第三步:
    // lambda表達式1 傳給 函數(shù)式接口:只讀取一行
    FileReadInterface fileReadInterface = reader -> reader.readLine();
// lambda表達式2 傳給 函數(shù)式接口:只讀取兩行
    FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
    // 最后一步: 不同的函數(shù)式接口的實現(xiàn),表現(xiàn)出不同的行為
       String str1 = processFile(fileReadInterface);
       String str2 = processFile(fileReadInterface2);
       System.out.println(str1);
       System.out.println(str2);
  }
// 第四步: 讀取文件方法,接受函數(shù)式接口作為參數(shù)
   public static String processFile(FileReadInterface fileReadInterface) throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
// 調(diào)用函數(shù)式接口中的抽象方法來處理數(shù)據(jù)
         return fileReadInterface.process(bufferedReader);
      }
  }
// 第一步:
 public static String processFile() throws IOException {
       try( BufferedReader bufferedReader =
                new BufferedReader(new FileReader("./test.txt"))){
         return bufferReader.readLine();
      }
  }


}

// 第二步: 我們手寫的函數(shù)式接口
@FunctionalInterface
interface FileReadInterface{
   String process(BufferedReader reader) throws IOException;
}

其實你會發(fā)現(xiàn),我們手寫的這個函數(shù)式接口,其實就是Function<T>去除泛型化后的接口,如下所示:

@FunctionalInterface
public?interface?Function<T,?R>?{
// 都是接受一個參數(shù),返回另一個參數(shù)
?R?apply(T?t);
}

下面我們列出Java中常用的一些函數(shù)式接口,你會發(fā)現(xiàn)自帶的已經(jīng)夠用了,基本不會需要我們自己去寫

這里的手寫只是為了自己實現(xiàn)一遍,可以加深理解程度

6. 常用的函數(shù)式接口

7. 什么是方法引用

我們先看一個例子

前面我們寫的lambda表達式,其實還可以簡化,比如

// 簡化前
Function<Cat,?Integer>?function?=?c->c.getAge();
// 簡化后
Function<Cat,?Integer>?function2?=?Cat::getAge;

其中簡化后的Cat::getAge,我們就叫做方法引用

方法引用就是引用類或?qū)ο蟮姆椒?/strong>;

下面我們列出方法引用的三種情況:

  • Object::instanceMethod(對象的實例方法)

  • Class::staticMethod(類的靜態(tài)方法)

  • Class::instanceMethod(類的實例方法)

像我們上面舉的例子就是第三種:類的實例方法

下面我們用代碼演示上面的三種方法:

public class ReferenceDemo {
   public static void main(String[] args) {
       // 第一種:引用對象的實例方法
       Cat cat = new Cat(1);
       Function<Cat, Integer> methodRef1 = cat::getSum;
       // 第二種:引用類的靜態(tài)方法
       Supplier<Integer> methodRef2 = Cat::getAverageAge;
       // 第三種:引用類的實例方法
       Function<Cat, Integer> methodRef3 = Cat::getAge;
  }
}
class Cat {
   int age;

   public Cat(int age) {
       this.age = age;
  }

   // 獲取貓的平均年齡
   public static int getAverageAge(){
       return 15;
  }
   // 獲取兩只貓的年齡總和
   public int getSum(Cat cat){
       return cat.getAge() + this.getAge();
  }

   public int getAge() {
       return age;
  }    public void setAge(int age) {
       this.age = age;
  }
}

為啥要用這個方法引用呢?

方法引用好比lambda表達式的語法糖,語法更加簡潔,清晰

一看就知道是調(diào)用哪個類或?qū)ο蟮哪膫€方法

8. 什么是構(gòu)造引用

上面介紹了方法引用,就是直接引用某個方法

這里的構(gòu)造引用同理可得,就是引用某個類的構(gòu)造方法

構(gòu)造引用的表達式為:Class::new,僅此一種

如果你有多個構(gòu)造函數(shù),那編譯器會自己進行推斷參數(shù)(你看看,多好,多簡潔)

比如下面的代碼:

// 這里調(diào)用 new Cat()
Supplier<Cat>?constructRef1?=?Cat::new;
// 這里調(diào)用 new Cat(Integer)
Function<Integer,?Cat>?constructRef2?=?Cat::new;

9. lambda表達式中引入外部變量的限制

要求引入lambda表達式中的變量,必須是最終變量,即該變量不會再被修改

比如下面的代碼:

public static void main(String[] args) {
 String str = "javalover.cc";
 Runnable runnable = ()->{
   str = "1";// 這里會報錯,因為修改了str引用的指向
   System.out.println(str);
}
}

可以看到,lambda表達式引用了外面的str引用,但是又在表達式內(nèi)部做了修改,結(jié)果就報錯了

為啥要有這個限制呢?

為了線程安全,因為lambda表達式有一個好處就是只在需要的時候才會執(zhí)行,而不是調(diào)用后立馬執(zhí)行

這樣就會存在多個線程同時執(zhí)行的并發(fā)問題

所以Java就從根源上解決:不讓變量被修改,都是只讀的

那你可能好奇,我不把str的修改代碼放到表達式內(nèi)部可以嗎?

也不行,道理是一樣的,只要lambda有用到這個變量,那這個變量不管是在哪里被修改,都是不允許的

不然的話,我這邊先執(zhí)行了一次lambda表達式,結(jié)果你就改了變量值,那我第二次執(zhí)行l(wèi)ambda,不就亂了嗎

10. lambda的組合操作

最后是lambda的必殺技:組合操作

在這里叫組合或者復(fù)合都可以

概述:組合操作就是先用一個lambda表達式,然后再在后面組合另一個lambda表達式,然后再在后面組合另另一個lambda表達式,然后。。。有點像是鏈?zhǔn)讲僮?/p>

學(xué)過JS的都知道Promise,里面的鏈?zhǔn)讲僮骶秃瓦@里的組合操作很像

用過Lombok的朋友,應(yīng)該很熟悉@Builder注解,其實就是構(gòu)造者模式

下面我們用代碼演示下組合操作:

// 重點代碼
public class ComposeDemo {
   public static void main(String[] args) {
       List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1));
       // 1. 先按年齡排序(默認遞增)
    // Dog::getAge, 上面介紹的方法引用
    // comparingInt, 是Comparator的一個靜態(tài)方法,返回Comparator<T>
    Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge);
       // 2. 如果有相同的年齡,則年齡相同的再按體重排序(如果年齡已經(jīng)比較出大小,則下面的體重就不會再去比較)
       Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);;
       // 3. 調(diào)用list對象的sort方法排序,參數(shù)是Comparator<? super Dog>
       list.sort(comparableAge.thenComparing(comparableWeight));
       System.out.println(list);
  }
}
// 非重點代碼
class Dog{
   private int age;
   private int weight;

   public Dog(int age, int weight) {
       this.age = age;
       this.weight = weight;
  }

   public int getAge() {
       return age;
  }

   public void setAge(int age) {
       this.age = age;
  }

   public int getWeight() {
       return weight;
  }

   public void setWeight(int weight) {
       this.weight = weight;
  }

   @Override
   public String toString() {
       return "Dog{" +
               "age=" + age +
               ", weight=" + weight +
               '}';
  }
}

輸出:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]

比較的流程如下所示:

總結(jié)

  • lambda的語法:  參數(shù)+符合+表達式或語句,比如(a,b)->{System.out.println("javalover.cc");}

  • 函數(shù)式接口:只有一個抽象方法,最好加@FunctionalInterface,這樣編譯器可及時發(fā)現(xiàn)錯誤,javadoc也說明這是一個函數(shù)式接口(可讀性)

  • 行為參數(shù)化:就是函數(shù)式接口作為參數(shù),然后再將lambda表達式傳給函數(shù)式接口,通過不同的lambda內(nèi)容實現(xiàn)不同的行為

  • 方法引用:lambda的語法糖,總共有三種:

    • Class::instanceMethod(類的實例方法)

    • Object::instanceMethod(對象的實例方法)

    • Class::staticMethod(類的靜態(tài)方法)

  • 構(gòu)造引用:就一種,編譯器自己可判斷是哪個構(gòu)造函數(shù),語法為Class::new

  • 在lambda中引入外部變量,必須保證這個變量是最終變量,即不再被修改

  • lambda的組合操作,就是鏈?zhǔn)讲僮?,組合是通過函數(shù)式接口的靜態(tài)方法來組合(靜態(tài)方法會返回另一個函數(shù)式接口的對象)

比如list.sort(comparableAge.thenComparing(comparableWeight));

到此這篇關(guān)于Java8中Lambda表達式的理解與應(yīng)用的文章就介紹到這了,更多相關(guān)Java8中Lambda表達式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論