Lambda語法詳解
我們在此抽象一下lambda表達式的一般語法:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
從lambda表達式的一般語法可以看出來,還是挺符合上面給出的非精確版本的定義–“一段帶有輸入參數的可執行語句塊”。
上面的lambda表達式語法可以認爲是最全的版本,寫起來還是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各種簡化版:
1. 參數類型省略–絕大多數情況,編譯器都可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:
(param1,param2, ..., paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
所以我們最開始的例子就變成了(省略了List的創建):
List<String> lowercaseNames = names.stream().map((name) -> {return name.toLowerCase();}).collect(Collectors.toList());
2. 當lambda表達式的參數個數只有一個,可以省略小括號。lambda表達式簡寫爲:
param1 -> {
statment1;
statment2;
//.............
return statmentM;
}
所以最開始的例子再次簡化爲:
List<String> lowercaseNames = names.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());
3. 當lambda表達式只包含一條語句時,可以省略大括號、return和語句結尾的分號。lambda表達式簡化爲:
param1 -> statment
所以最開始的例子再次簡化爲:
List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());
4. 使用Method Reference(具體語法後面介紹)
//注意,這段代碼在Idea 13.0.2中顯示有錯誤,但是可以正常運行
List<String> lowercaseNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());
我們前面所有的介紹,感覺上lambda表達式像一個閉關鎖國的傢伙,可以訪問給它傳遞的參數,也能自己內部定義變量。但是卻從來沒看到其訪問它外部的變量。是不是lambda表達式不能訪問其外部變量?我們可以這樣想:lambda表達式其實是快速創建SAM接口的語法糖,原先的SAM接口都可以訪問接口外部變量,lambda表達式肯定也是可以(不但可以,在java8中還做了一個小小的升級,後面會介紹)。
Lambda表達式眼中的外部世界
String[] array = {"a", "b", "c"};
for(Integer i : Lists.newArrayList(1,2,3)){
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}
上面的這個例子中,map中的lambda表達式訪問外部變量Integer i。並且可以訪問外部變量是lambda表達式的一個重要特性,這樣我們可以看出來lambda表達式的三個重要組成部分:
- 輸入參數
- 可執行語句
- 存放外部變量的空間
不過lambda表達式訪問外部變量有一個非常重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。
String[] array = {"a", "b", "c"};
for(int i = 1; i<4; i++){
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
}
上面的代碼,會報編譯錯誤。因爲變量i被lambda表達式引用,所以編譯器會隱式的把其當成final來處理(ps:大家可以想象問什麼上一個例子不報錯,而這個報錯。)細心的讀者肯定會發現不對啊,以前java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。Bingo,在java8對這個限制做了優化(前面說的小小優化),可以不用顯示使用final修飾,但是編譯器隱式當成final來處理。
Stream.of(array).map(item -> Strings.padEnd(item, i, '@')).forEach(System.out::println);
lambda眼中的this
在lambda中,this不是指向lambda表達式產生的那個SAM對象,而是聲明它的外部對象。
Java8 新增的函數式api存放於 java.util.function 下,提供了大多數常用的函數式接口
1、Predicate:斷言型接口
// 傳入的字符串是否以 .sql 結尾
Predicate<String> isEndWithSql = (s) -> s.endsWith(".sql");
// 傳入的字符串非 .sql 結尾
Predicate<String> notEndWithSql = isEndWithSql.negate();
boolean test = isEndWithSql.test("test.sql");
System.out.println(test);
boolean test1 = notEndWithSql.test("test.sql");
System.out.println(test1);
// 判斷集合是否爲空
Predicate<List<String>> isEmptyList = List::isEmpty;
2、Function:功能型接口
// 字符串轉爲 Integer
Function<String,Integer> toInteger = s -> Integer.valueOf(s);
System.out.println(toInteger.apply("222"));
toInteger = Integer::valueOf;
System.out.println(toInteger.apply("222"));
Function 中的 default 方法:
andThen:在 Function 執行之後
compose:在 Function 執行之前
3、Supplier:供給型接口
Supplier<StringBuilder> sbSupplier = StringBuilder::new;
StringBuilder sb = sbSupplier.get();
4、Consumer:消費型接口
Consumer<Runnable> runnableConsumer = (run) -> new Thread(run).start();
runnableConsumer.accept(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("測試一下了");
});
public void test(){
Consumer<String> con = System.out::println;
con.accept("測試一下了");
}
public void test2(){
Supplier<Date> sup = Date::new;
Date date1 = sup.get();
Date date2 = sup.get();
System.out.println(date1 == date2);
}
public void test3(){
Function<Integer,String[]> fun = String[]::new;
String[] strArr = fun.apply(10);
System.out.println(strArr);
}
public void test4(){
Predicate<Integer> predicate = x -> x > 10;
boolean test = predicate.test(10);
System.out.println(test);
}