最近剛好有空給大家整理下JDK8的特性,這個在實際開發中的作用也是越來越重了,本文重點講解下Lambda表示式

Lambda表示式

  Lambda 表示式,也可稱為閉包,它是推動 Java 8 釋出的最重要新特性。

  Lambda 允許把函式作為一個方法的引數(函式作為引數傳遞進方法中)。

  使用 Lambda 表示式可以使程式碼變的更加簡潔緊湊。

1. 需求分析

   建立一個新的執行緒,指定執行緒要執行的任務

    public static void main(String[] args) {
// 開啟一個新的執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新執行緒中執行的程式碼 : "+Thread.currentThread().getName());
}
}).start();
System.out.println("主執行緒中的程式碼:" + Thread.currentThread().getName());
}

程式碼分析:

  1. Thread類需要一個Runnable介面作為引數,其中的抽象方法run方法是用來指定執行緒任務內容的核心
  2. 為了指定run方法體,不得不需要Runnable的實現類
  3. 為了省去定義一個Runnable 的實現類,不得不使用匿名內部類
  4. 必須覆蓋重寫抽象的run方法,所有的方法名稱,方法引數,方法返回值不得不都重寫一遍,而且不能出錯,
  5. 而實際上,我們只在乎方法體中的程式碼

一起來進階提升吧:463257262

2.Lambda表示式初體驗

  Lambda表示式是一個匿名函式,可以理解為一段可以傳遞的程式碼

new Thread(() -> { System.out.println("新執行緒Lambda表示式..." +Thread.currentThread().getName()); })
.start();

  Lambda表示式的優點:簡化了匿名內部類的使用,語法更加簡單。

  匿名內部類語法冗餘,體驗了Lambda表示式後,發現Lambda表示式是簡化匿名內部類的一種方式。

3. Lambda的語法規則

  Lambda省去了面向物件的條條框框,Lambda的標準格式由3個部分組成:

(引數型別 引數名稱) -> {
程式碼體;
}

格式說明:

  • (引數型別 引數名稱):引數列表
  • {程式碼體;} :方法體
  • -> : 箭頭,分割引數列表和方法體

3.1 Lambda練習1

  練習無參無返回值的Lambda

定義一個介面

public interface UserService {
void show();
}

然後建立主方法使用

public class Demo03Lambda {

    public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
System.out.println("show 方法執行了...");
}
});
System.out.println("----------");
goShow(() -> { System.out.println("Lambda show 方法執行了..."); });
} public static void goShow(UserService userService){
userService.show();
}
}

輸出:

show 方法執行了...
----------
Lambda show 方法執行了...

3.2 Lambda練習2

   完成一個有參且有返回值得Lambda表示式案例

建立一個Person物件

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person { private String name; private Integer age; private Integer height; }

  然後我們在List集合中儲存多個Person物件,然後對這些物件做根據age排序操作

    public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("周杰倫",33,175));
list.add(new Person("劉德華",43,185));
list.add(new Person("周星馳",38,177));
list.add(new Person("郭富城",23,170)); Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
for (Person person : list) {
System.out.println(person);
}
}

  我們發現在sort方法的第二個引數是一個Comparator介面的匿名內部類,且執行的方法有引數和返回值,那麼我們可以改寫為Lambda表示式

    public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("周杰倫",33,175));
list.add(new Person("劉德華",43,185));
list.add(new Person("周星馳",38,177));
list.add(new Person("郭富城",23,170)); /*Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
for (Person person : list) {
System.out.println(person);
}*/
System.out.println("------");
Collections.sort(list,(Person o1,Person o2) -> {
return o1.getAge() - o2.getAge();
});
for (Person person : list) {
System.out.println(person);
}
}

輸出結果

Person(name=郭富城, age=23, height=170)
Person(name=周杰倫, age=33, height=175)
Person(name=周星馳, age=38, height=177)
Person(name=劉德華, age=43, height=185)

4. @FunctionalInterface註解

  @FunctionalInterface是JDK8中新增加的一個函式式註解,表示該註解修飾的介面只能有一個抽象方法。

/**
* @FunctionalInterface
* 這是一個函式式註解,被該註解修飾的介面只能宣告一個抽象方法
*/
@FunctionalInterface
public interface UserService { void show(); }

5. Lambda表示式的原理

  匿名內部類的本質是在編譯時生成一個Class 檔案。XXXXX$1.class

public class Demo01Lambda {

    public static void main(String[] args) {
// 開啟一個新的執行緒
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新執行緒中執行的程式碼 : "+Thread.currentThread().getName());
}
}).start();
System.out.println("主執行緒中的程式碼:" + Thread.currentThread().getName());
System.out.println("---------------");
/*new Thread(() -> { System.out.println("新執行緒Lambda表示式..." +Thread.currentThread().getName()); })
.start();*/
}
}

  還可以通過反編譯工具來檢視生成的程式碼 XJad 工具來檢視

static class Demo01Lambda$1
implements Runnable
{ public void run()
{
System.out.println((new StringBuilder()).append("新執行緒中執行的程式碼 : " ).append(Thread.currentThread().getName()).toString());
} Demo01Lambda$1()
{
}
}

  那麼Lambda表示式的原理是什麼呢?我們也通過反編譯工具來檢視



  寫的有Lambda表示式的class檔案,我們通過XJad檢視報錯。這時我們可以通過JDK自帶的一個工具:javap 對位元組碼進行反彙編操作。

javap -c -p 檔名.class

-c:表示對程式碼進行反彙編
-p:顯示所有的類和成員

反彙編的結果:

E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes\com\bobo\jdk\lambda>javap -c -p Demo03Lambda.class
Compiled from "Demo03Lambda.java"
public class com.bobo.jdk.lambda.Demo03Lambda {
public com.bobo.jdk.lambda.Demo03Lambda();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:show:()Lcom/bobo/jdk/lambda/service/UserService;
5: invokestatic #3 // Method goShow:(Lcom/bobo/jdk/lambda/service/UserService;)V
8: return public static void goShow(com.bobo.jdk.lambda.service.UserService);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod com/bobo/jdk/lambda/service/UserService.show:()V
6: return private static void lambda$main$0();
Code:
0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda show 方法執行了...
5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}

  在這個反編譯的原始碼中我們看到了一個靜態方法 lambda$main$0(),這個方法裡面做了什麼事情呢?我們通過debug的方式來檢視下:

上面的效果可以理解為如下:

public class Demo03Lambda {

    public static void main(String[] args) {
....
} private static void lambda$main$0();
System.out.println("Lambda show 方法執行了...");
}
}

  為了更加直觀的理解這個內容,我們可以在執行的時候新增 -Djdk.internal.lambda.dumpProxyClasses, 加上這個引數會將內部class碼輸出到一個檔案中

java -Djdk.internal.lambda.dumpProxyClasses 要執行的包名.類名

命令執行

E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.bobo.jdk.lambda.Demo03Lambda
Lambda show 方法執行了...



反編譯後的內容:

  可以看到這個匿名的內部類實現了UserService介面,並重寫了show()方法。在show方法中呼叫了Demo03Lambda.lambda$main$0(),也就是呼叫了Lambda中的內容。

public class Demo03Lambda {

    public static void main(String[] args) {
goShow(new UserService() {
@Override
public void show() {
Demo03Lambda.lambda$main$0();
}
});
System.out.println("----------"); } public static void goShow(UserService userService){
userService.show();
} private static void lambda$main$0();
System.out.println("Lambda show 方法執行了...");
}
}

小結:

匿名內部類在編譯的時候會產生一個class檔案。

Lambda表示式在程式執行的時候會形成一個類。

  1. 在類中新增了一個方法,這個方法的方法體就是Lambda表示式中的程式碼
  2. 還會形成一個匿名內部類,實現介面,重寫抽象方法
  3. 在介面中重寫方法會呼叫新生成的方法

6.Lambda表示式的省略寫法

  在lambda表示式的標準寫法基礎上,可以使用省略寫法的規則為:

  1. 小括號內的引數型別可以省略
  2. 如果小括號內有且僅有一個引數,則小括號可以省略
  3. 如果大括號內有且僅有一個語句,可以同時省略大括號,return 關鍵字及語句分號。
public class Demo05Lambda {

    public static void main(String[] args) {
goStudent((String name,Integer age)->{
return name+age+" 6666 ...";
});
// 省略寫法
goStudent((name,age)-> name+age+" 6666 ...");
System.out.println("------");
goOrder((String name)->{
System.out.println("--->" + name);
return 666;
});
// 省略寫法
goOrder(name -> {
System.out.println("--->" + name);
return 666;
});
goOrder(name -> 666);
} public static void goStudent(StudentService studentService){
studentService.show("張三",22);
} public static void goOrder(OrderService orderService){
orderService.show("李四");
} }

7.Lambda表示式的使用前提

  Lambda表示式的語法是非常簡潔的,但是Lambda表示式不是隨便使用的,使用時有幾個條件要特別注意

  1. 方法的引數或區域性變數型別必須為接口才能使用Lambda
  2. 介面中有且僅有一個抽象方法(@FunctionalInterface)

8.Lambda和匿名內部類的對比

  Lambda和匿名內部類的對比

  1. 所需型別不一樣

    • 匿名內部類的型別可以是 類,抽象類,介面
    • Lambda表示式需要的型別必須是介面
  2. 抽象方法的數量不一樣

    • 匿名內部類所需的介面中的抽象方法的數量是隨意的
    • Lambda表示式所需的介面中只能有一個抽象方法
  3. 實現原理不一樣

    • 匿名內部類是在編譯後形成一個class
    • Lambda表示式是在程式執行的時候動態生成class

~好了,Lambda表示式的內容就介紹到這兒,如果對你有幫助,歡迎點贊關注加收藏哦 V_V