在上一篇《公子奇帶你一步一步了解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 }