最近剛好有空給大家整理下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