本章內容:
Lambda管中窺豹
在哪里以及如何使用Lambda表達式
函數式接口,類型推斷
方法引用
Lambda復合
2.1 Lambda管中窺豹
可以把Lambda表達式理解為簡潔的表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、方法主體、返回類型等。
匿名:
它不像普通方法那樣有一個明確的名稱,寫得少做得多
函數:
Lambda函數不像方法那樣屬於某個特定的類,但它和方法一樣有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表
傳遞:
Lambda表達式可以作為參數傳遞給方法
簡潔:
不需要像匿名類那樣寫很多的代碼模板
參數列表:這里采用了Comparator中compare方法的參數,兩個Apple
箭頭:箭頭 -> 將參數列表與Lambda主體分隔開
Lambda主體:比較兩個Apple的重量,表達式就是Lambda的返回值
測試:
下面哪個不是有效的Lambda表達式:
1. () -> {}
2. () -> "Hello"
3. () -> {return "Hello";}
4. (Integer i) -> return "Hello" + i;
5. (String s) -> {"Hello";}
答案:
4和5不是有效的Lambda表達式,4:(Integer i) -> {return "Hello" + i;};5:(String s) -> {return "Hello";}
Lambda示例:
2.2 在哪里以及如何使用Lambda
2.2.1 函數式接口:只定義了一個抽象方法的接口
例如之前定義的Predicate<T>就是一個函數式接口,Comparator和Runnable也是函數式接口。
那使用函數式接口能干什么呢?Lambda表達式允許你直接以內聯的形式為函數式接口的抽象方法提供實現,並把整個表達式作為函數式接口的實例。
public class LambdaDemo { public static void main(String[] args) { Runnable r1 = () -> System.out.println("Hello World1"); Runnable r2 = new Runnable() { @Override public void run() { System.out.println("Hello World2"); } }; process(r1); process(r2); process(() -> System.out.println("Hello World3")); } public static void process(Runnable r) { r.run(); } }
2.2.2 函數描述符
函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。我們將這種抽象方法叫做函數描述符。
2.3 使用函數式接口
2.3.1 Predicate
java.util.function.Predicate<T>接口定義了一個名叫test的抽象方法,它接受一個泛型T對象,並返回一個boolean
public static <T> List<T> filter(List<T> list, Predicate<T> p){ List<T> result = new ArrayList<T>(); for (T t : list) { if(p.test(t)) { result.add(t); } } return result; } // 測試 // 使用Predicate System.out.println("使用Predicate:"); List<String> list = new ArrayList<String>(); list.add(null); list.add("Hello World"); list.add(""); Predicate<String> p = (String s) -> s != null && !s.isEmpty(); System.out.println(filter(list , p));
2.3.2 Consumer
java.util.function.Consumer<T>定義了一個叫accept的抽象方法,它接受一個泛型T的對象,並沒有返回(void)
public static <T> void forEach(List<T> list, Consumer<T> c) { for(T t : list) { c.accept(t); } } // 測試 // 使用Consumer System.out.println("// 使用Consumer:"); forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));
2.3.3 Function
java.util.function.Function<T, R>定義了一個叫apply的抽象方法,它接受一個泛型T的對象,並返回一個泛型R對象
public static <T, R> List<R> map(List<T> list, Function<T, R> f){ List<R> result = new ArrayList<R>(); for (T t : list) { result.add(f.apply(t)); } return result; } // 測試 // 使用Function System.out.println("// 使用Function:"); List<Integer> l = map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length()); System.out.println(l);
2.4 類型檢查
Lambda的類型是從使用Lambda的上下文推斷出來的,下面描述了Lambda表達式的類型檢查過程
2.5 類型推斷
你還可以進一步簡化你的代碼。java編譯器會從上下文(目標類型)推斷出用什么函數式接口來配合Lambda表達式,這意味着它也可以推斷出適合Lambda的簽名,因為函數描述符可以通過目標類型來獲取。這樣就可以在Lambda語法中省去參數類型的聲明。
List<Apple> heavirThan150 = filter(inventory, a -> a.getWeight() > 150);
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
參數沒有顯式類型。有時候顯式寫出參數類型更易讀,這個取決於程序員的選擇。
2.6 方法的引用
方法的引用讓你可以重復使用現有的方法定義,並像Lambda一樣傳遞他們。
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing((Apple a) -> a.getWeight()));
inventory.sort(Comparator.comparing(Apple::getWeight));//你的第一個方法的引用
2.6.1 管中窺豹
當你需要使用方法的引用時,目標引用放在分隔符 :: 前,方法的名稱放在后面。例如 Apple::getWeight 就是引用了Apple類中的getWeight 方法。
Lambda 及其等效方法引用的例子:
Lambda | 等效的方法引用 |
(Apple a) -> a.getWeight() | Apple::getWeight |
(str, i) -> str.substring(i) | String::substring |
(String s) -> System.out.println(s); | System.out::println |
BiFunction<String, Integer, String> bf = (String str, Integer i) -> str.substring(i);
bf = String::substring; String methadQuote = methadQuote("Hello", 2, bf); System.out.println("方法的引用:"); System.out.println(methadQuote); Consumer<String> consumer = (String str) -> System.out.println(str); consumer = System.out::println; methadQuote1("Hello World", consumer);
// 方法的引用
public static String methadQuote(String str, Integer i, BiFunction<String, Integer, String> bf) {
return bf.apply(str, i);
}
public static void methadQuote1(String str, Consumer<String> c) {
c.accept(str);
}
如何構建方法的引用:
方法引用主要有三類:
1. 指向靜態方法的方法引用(例如Integer的parseInt方法,寫成 Integer::parseInt)
2. 指向任意類型實例方法的方法引用(例如String的length方法,寫成String::length)
3. 指向現有對象的實例方法的方法引用(例如Apple對象a有getWeight方法,寫成Apple::getWeight)
解釋一下第二種:類似於String::length這種引用思想就是你在引用一個對象的方法,而這個對象本身是Lambda的一個參數。(這個對象必須放在泛型參數的第一位--不確定)
2.6.2 構造函數引用
對於一個現有的構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用:ClassName::new。它適合Supplier的簽名 () -> T
/**
* 構造函數引用
*/
// 無參
Supplier<Apple> s = Apple::new;
// 一個參數
Function<String, Apple> f = Apple::new;
// 兩個參數
BiFunction<Integer, String, Apple> biFunction = Apple::new;
如果想要為更多參數的構造函數使用方法引用怎么辦呢?
答案:
自己創建這樣的函數式接口:
private String name; private Integer age; private String address; public interface TriFunction<T, U, V, R> { R apple(T t, U u, V v); } // 自定義的函數式接口 TriFunction<String, Integer, String, Student> tri = Student::new;
2.7 復合Lambda表達式的有用方法
即將講到的方法為默認方法,后面章節會細講
2.7.1 比較器復合
Comparator<Apple> com = Comparator.comparing(Apple::getWeight);
1. 逆序
inventory.sort(Comparator.comparing(Apple::getWeight).reversed());
2. 比較器鏈
inventory.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));
2.7.2 謂詞復合
謂詞接口包含三個方法:negate,and,or
Predicate<Apple> redApple = (Apple a) -> "red".equals(a.getColor()); Predicate<Apple> notRedApple = redApple.negate();// 產生現有對象redApple的非 Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150); Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".equals(a.getColor()));
注意:and和or在表達式中按照從左到右確定優先級。因此,a.or(b).and(c) 為 (a || b) && c
2.7.3 函數復合:
你還可以把Function接口所代表的的Lambda表達式復合起來。Function接口為此配了andThen和compose兩個默認方法,他們都返回一個Function的實例。
Function<Integer, Integer> g = x -> x + 1; Function<Integer, Integer> h = y -> y * 2; Function<Integer, Integer> j = g.andThen(h);// 相當於數學中的 h(g(x)) Integer apply = j.apply(1); System.out.println("andThen函數復合:" + apply);// 4 j = g.compose(h);// 相當於數學中的 g(h(x)) apply = j.apply(1); System.out.println("compose函數復合:" + apply);// 3
備注:
摘自文獻:《Java8實戰》(中文版)《Java8 in Action》(英文版)
代碼(GitHub地址): https://github.com/changlezhong/java8InAction