作為有經驗的程式設計師如果不懂Lambda表達式就說不過去了吧,建議收藏!!!
最近剛好有空給大家整理下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());
}
程式碼分析:
- Thread類需要一個Runnable介面作為參數,其中的抽象方法run方法是用來指定執行緒任務內容的核心
- 為了指定run方法體,不得不需要Runnable的實現類
- 為了省去定義一個Runnable 的實現類,不得不使用匿名內部類
- 必須覆蓋重寫抽象的run方法,所有的方法名稱,方法參數,方法返回值不得不都重寫一遍,而且不能出錯,
- 而實際上,我們只在乎方法體中的程式碼
一起來進階提升吧: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表達式在程式運行的時候會形成一個類。
- 在類中新增了一個方法,這個方法的方法體就是Lambda表達式中的程式碼
- 還會形成一個匿名內部類,實現介面,重寫抽象方法
- 在介面中重寫方法會調用新生成的方法
6.Lambda表達式的省略寫法
在lambda表達式的標準寫法基礎上,可以使用省略寫法的規則為:
- 小括弧內的參數類型可以省略
- 如果小括弧內有且僅有一個參數,則小括弧可以省略
- 如果大括弧內有且僅有一個語句,可以同時省略大括弧,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表達式不是隨便使用的,使用時有幾個條件要特別注意
- 方法的參數或局部變數類型必須為介面才能使用Lambda
- 介面中有且僅有一個抽象方法(@FunctionalInterface)
8.Lambda和匿名內部類的對比
Lambda和匿名內部類的對比
-
所需類型不一樣
- 匿名內部類的類型可以是 類,抽象類,介面
- Lambda表達式需要的類型必須是介面
-
抽象方法的數量不一樣
- 匿名內部類所需的介面中的抽象方法的數量是隨意的
- Lambda表達式所需的介面中只能有一個抽象方法
-
實現原理不一樣
- 匿名內部類是在編譯後形成一個class
- Lambda表達式是在程式運行的時候動態生成class
~好了,Lambda表達式的內容就介紹到這兒,如果對你有幫助,歡迎點贊關注加收藏哦 V_V