1. 程式人生 > >公子奇帶你一步一步瞭解Java8中Lambda表示式

公子奇帶你一步一步瞭解Java8中Lambda表示式

在上一篇《公子奇帶你一步一步瞭解Java8中行為引數化》中,我們演示到最後將匿名實現簡寫為

1 (Police police) -> "浙江".equals(police.getPoliceNativePlace());

這是一個帶有箭頭的函式,這種寫法在Java8中即為Lambda表示式。那麼我們就來好好的講講Lambda表示式。

一 、什麼是Lambda表示式

首先我們需要知道Lambda表示式時JDK8中提出的編碼風格,它可以簡潔地表示可作為引數傳遞的匿名函式的一種方式,也可以理解為匿名實現的一種,關於匿名物件的特徵它也是有的,例如:它沒有名稱,但它有引數列表、函式主體、返回型別,可能還有一個可以丟擲的異常列表。基本語法可以使用以下兩種方式表示:

1 (parameters) -> expression 
2 或 
3 (parameters) -> { statements; }

由上可知Lambda表示式有三個部分:

1、引數列表

2、箭頭 “ -> ” 用來將引數和主體連線到一起

3、Lambda主體

以下我們通過一些案例來表示一個有效的Lambda表示式

1 () -> "ABC";  //沒有引數但有返回值ABC,return 關鍵字自動忽略
2 () -> {};     //沒有引數,方法體不執行任何操作
3 () -> {return "ABC"}; //沒有引數,返回ABC,有大括號,需要顯示return
4 (String s) -> s.length(); //有一個引數,並對引數進行操作
5 (int x, int y) -> {       //有兩個引數,並做複雜操作,需要大括號
6     System.out.println("result:");
7     System.out.println(x + y);
8 }
9 (String s1, String s2) -> s1.compareTo(s2) //對兩個引數操作

二、什麼是函式式介面

在上一篇文章中,我們最後的Lambda表示式是作為一個引數傳遞給filter方法的,同時在filter方法中的第二個引數是一個介面Predicate<T>,這個介面在JDK8中我們就將其稱為函式式介面,回看這個介面只有一個抽象方法。即函式式介面就是隻定義一個抽象方法的介面。同時在JDK8中也使用了註解 @FunctionInterface 將一個介面標註為函式式介面,當然沒有新增該註解也可為函式式介面,只是新增後程序在執行時會進行檢測,否則會丟擲異常。同時為了實現更靈活的操作,介面中可以新增靜態方法和預設方法(即JDK8之後,介面中是可以定義方法實現的)。

 1 package com.hz;
 2 
 3 /**
 4  * 在1.8之前,我們一直強調介面中不可有方法實現
 5  * 1.8之後是可以在介面中定義預設方法和靜態方法
 6  */
 7 public interface InterfaceMethod {
 8 
 9     default void method1() {
10         System.out.println("介面的預設方法實現...");
11     }
12 
13     static void method2() {
14         System.out.println("介面的靜態方法實現...");
15     }
16 
17     public static void main(String[] args) {
18         InterfaceMethod.method2();
19 
20         new InterfaceMethod() {}.method1();
21     }
22 }
23 
24 //官方提供的一個函式式介面
25 package java.util.function;
26 
27 import java.util.Objects;
28 
29 @FunctionalInterface
30 public interface Predicate<T> {
31     boolean test(T t);
32 
33     default Predicate<T> and(Predicate<? super T> other) {
34         Objects.requireNonNull(other);
35         return (t) -> test(t) && other.test(t);
36     }
37 
38     default Predicate<T> negate() {
39         return (t) -> !test(t);
40     }
41 
42     default Predicate<T> or(Predicate<? super T> other) {
43         Objects.requireNonNull(other);
44         return (t) -> test(t) || other.test(t);
45     }
46 
47     static <T> Predicate<T> isEqual(Object targetRef) {
48         return (null == targetRef)
49                 ? Objects::isNull
50                 : object -> targetRef.equals(object);
51     }
52 }
53 
54 //說明:從官網的Predicate介面中我們可以發現除了多了註解和一些方法實現,與我們上一講自己定義的Predicate介面很類似

三、為什麼提出函式式介面

    可能到這裡你也發現了,既然Lambda表示式已經很簡潔的表達了實現,我們為什麼還需要引入函式式介面的概念呢?為了簡化程式碼和靈活運用,我們提出了Lambda表示式的概念,Lambda表示式允許你直接以內聯的形式為函式式介面的抽象方法提供實現,並把整個表示式作為函式式介面的例項。由此即可理解:Lambda表示式是函式式介面的一種實現。

    在JDK8中我們會發現大部分函式式介面都添加了 @FunctionInterface 註解,但我們不能僅僅理解為只有添加了該註解的才為函式式介面,我們應該理解的是隻有一個抽象方法的接口才為函式式介面。

四、應用場景

    既然Lambda表示式這麼好,那麼我們應該在哪裡去使用呢?下面介紹一些常用的:

    1、列表迴圈操作

 1 package com.hz;
 2 
 3 import java.util.Arrays;
 4 import java.util.List;
 5 import java.util.function.Consumer;
 6 
 7 public class LambdaTest {
 8     public static void main(String[] args) {
 9         List<String> ss = Arrays.asList("gong", "zi", "qi", "666");
10 
11         //列印列表中每個值長度
12         //---匿名實現
13         ss.forEach(new Consumer<String>() {
14             @Override
15             public void accept(String s) {
16                 System.out.println(s.length());
17             }
18         });
19         
20         System.out.println("------------------");
21         
22         //--遍歷取值
23         for (String s : ss) {
24             System.out.println(s.length());
25         }
26 
27         System.out.println("---------");
28 
29         //--Lambda表示式
30         ss.forEach((String s) -> System.out.println(s.length()));
31     }
32 }
33 //哪種方式簡潔、容易理解很明顯

    2、事件監聽

 1 //監聽實現一
 2 Button button = new Button("提交");
 3 button.addActionListener(new ActionListener() {
 4     @Override
 5     public void actionPerformed(ActionEvent e) {
 6         System.out.println("使用者點選了提交按鈕");
 7     }
 8 });
 9 
10 //監聽實現二
11 button.addActionListener(e -> {
12     System.out.println("使用者點選了提交按鈕");
13 });

    3、函式式介面(回看上一篇文章)

    更多應用場景,我們在後續文章中再整理。

五、引出方法引用

    我們繼續回看上一篇,發現在最後呼叫表示式是何其的相似

1 Police police) -> police.getPoliceAge() > 30;
2 
3 System.out.println("---------------");
4 
5 (Police police) -> "浙江".equals(police.getPoliceNativePlace());

    那麼在JDK8中還可以再次簡化嗎?答案是肯定的,這就是方法引用。即

1 Police :: getPoliceAge;
2 String :: equals;

    方法引用的加入可以讓我們重複使用現有的方法定義,並像Lambda一樣傳遞它們。我們同時可理解為方法引用是針對僅僅涉及單一方法的Lambda的語法糖。

    那麼什麼情況下,我們可以及如何構建方法引用?

      1、指向靜態方法的方法引用。

      2、指向任意型別例項方法的方法引用。

      3、指向現有物件的例項方法的方法引用。

六、複合Lambda表示式組裝

    在實際開發中,我們不可能只操作一種或一個Lambda表示式,一個表示式的輸出可能會是另一個表示式的輸入,兩個條件的同時滿足(例如上一篇中籍貫為浙江年齡大於30的民警),或多個條件只要有一個合適即命中(例如上一篇中籍貫為浙江或年齡大於30的民警)等。如此我們將其分為3類。

    1、比較器複合

繼續回到上一篇文章,如何對民警年齡進行排列。

 1 public static void main(String[] args) {
 2     List<Police> polices = Arrays.asList(new Police("P001", "餘警官", 27, "浙江"),
 3             new Police("P002", "李警官", 32, "安徽"),
 4             new Police("P003", "程警官", 25, "安徽"),
 5             new Police("P004", "楊警官", 35, "浙江"),
 6             new Police("P005", "楊警官", 31, "上海"));
 7 
 8     polices.sort(comparing(Police :: getPoliceAge).reversed());
 9 
10     System.out.println("結果1: " + polices);
11 }

    2、謂詞複合

1 Predicate<Police> p = (Police police) -> police.getPoliceAge() > 30;
2 Predicate<Police> p2 = p.and((Police police) -> "浙江".equals(police.getPoliceNativePlace()));
3 List<Police> result = filter(polices, p2);
4 System.out.println(result);
5 
6 //當然 除了 and  還有 or方法

    3、函式複合

1 Function<Integer, Integer> f = x -> x + 1;
2 Function<Integer, Integer> g = x -> x * 2;
3 Function<Integer, Integer> h = f.andThen(g);
4 int result = h.apply(1);
5 System.out.println(result);

七、一個例項

    回到上一篇文章場景,將按照一定條件得到的民警按照年齡、籍貫排序。

 1 package com.hz;
 2  
 3 import java.util.ArrayList;
 4 import java.util.Arrays;
 5 import java.util.List;
 6 import java.util.function.Predicate;
 7 
 8 import static java.util.Comparator.comparing;
 9 
10 public class PoliceMain {
11     public static void main(String[] args) {
12         List<Police> polices = Arrays.asList(new Police("P001", "餘警官", 27, "浙江"),
13                 new Police("P002", "李警官", 32, "安徽"),
14                 new Police("P003", "程警官", 25, "安徽"),
15                 new Police("P004", "楊警官", 35, "浙江"),
16                 new Police("P005", "張警官", 31, "上海"),
17                 new Police("P006", "王警官", 42, "浙江"),
18                 new Police("P007", "趙警官", 31, "浙江"),
19                 new Police("P008", "劉警官", 49, "浙江"),
20                 new Police("P009", "周警官", 32, "浙江"));
21 
22         //問題:找出籍貫為浙江並且年齡大於30歲的民警或者籍貫為安徽的民警,按照民警年齡排序,若年齡相同按照籍貫排序
23         Predicate<Police> p1 = (Police police) -> police.getPoliceAge() > 30;
24 
25         Predicate<Police> p2 = p1.and((Police police) -> "浙江".equals(police.getPoliceNativePlace()));
26 
27         Predicate<Police> p3 = p2.or((Police police) -> "安徽".equals(police.getPoliceNativePlace()));
28 
29         List<Police> result = filter(polices, p3);
30 
31         result.sort(comparing(Police :: getPoliceAge).thenComparing(Police :: getPoliceNativePlace));
32 
33         System.out.println("結果: " + result);
34     }
35 
36     static <T> List<T> filter(List<T> con, Predicate<T> p) {
37         List<T> result = new ArrayList<>();
38 
39         for (T e : con) {
40             if (p.test(e)) {
41                 result.add(e);
42             }
43         }
44 
45         return result;
46     }
47 
48 }
49 
50 // 以上方式需要我們自己去定義一個filter方法  或按照以下方式
51 
52 package com.hz;
53 
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.List;
57 import java.util.function.Function;
58 import java.util.function.Predicate;
59 
60 import static java.util.Comparator.comparing;
61 
62 public class PoliceMain {
63     public static void main(String[] args) {
64         List<Police> polices = Arrays.asList(new Police("P001", "餘警官", 27, "浙江"),
65                 new Police("P002", "李警官", 32, "安徽"),
66                 new Police("P003", "程警官", 25, "安徽"),
67                 new Police("P004", "楊警官", 35, "浙江"),
68                 new Police("P005", "張警官", 31, "上海"),
69                 new Police("P006", "王警官", 42, "浙江"),
70                 new Police("P007", "趙警官", 31, "浙江"),
71                 new Police("P008", "劉警官", 49, "浙江"),
72                 new Police("P009", "周警官", 32, "浙江"));
73 
74         //問題:找出籍貫為浙江並且年齡大於30歲的民警或者籍貫為安徽的民警,按照民警年齡排序,若年齡相同按照籍貫排序
75         Predicate<Police> p1 = (Police police) -> police.getPoliceAge() > 30;
76 
77         Predicate<Police> p2 = p1.and((Police police) -> "浙江".equals(police.getPoliceNativePlace()));
78 
79         Predicate<Police> p3 = p2.or((Police police) -> "安徽".equals(police.getPoliceNativePlace()));
80 
81         Function<List<Police>, List<Police>> f = (List<Police> list) -> {
82             List<Police> temp = new ArrayList<>();
83             for (Police police : list) {
84                 if (p3.test(police)) {
85                     temp.add(police);
86                 }
87             }
88             return temp;
89         };
90 
91         List<Police> result = f.apply(polices);
92 
93         result.sort(comparing(Police :: getPoliceAge).thenComparing(Police :: getPoliceNativePlace));
94 
95         System.out.println("結果: " + result);
96     }
97 
98 }