一直感覺Lambda表達式是十分“高大上”的一種技術,不管是衝着其本身可以將代碼量縮短至變態的緣故,還是希望拿來當做裝逼神器的工具(開玩笑!),都值得好好學習一波。
好了,言歸正傳!Lambda表達式的格式不外乎如下:
(parameters) -> expression 或 (parameters) ->{ statements; }
語法格式和傳統的方法其實一樣,箭頭左邊就是參數列表,當然也可以是爲空;箭頭右邊就是表達式或者代碼段了。之所以推出lambda表達式,就是允許你通過表達式來代替功能接口。
需要特別注意的是,lambda表達式使用是有前提的,那就是隻有函數式接口的方法才能能用lambda作爲其調用時的方法。具體什麼意思呢?首先來看下什麼是函數式接口?
函數式接口其實本質上還是一個接口,但是它是一種特殊的接口:SAM類型的接口(Single Abstract Method)。也就是說,該接口只有一個抽象方法,多了不行,少了也不行。可以有靜態方法和默認方法,因爲這兩種方法都是已經實現的了。同時,這個抽象方法一定不能跟Object類中的方法同名。Java8新特性提供了函數式接口,用於更好的支持函數式編程。(具體瞭解函數式接口可以參考另一篇博客Java 8 函數式接口和Lambda學習筆記(二))
下面將從簡單到複雜的過程來系統學習一下lambda表達式,希望能揭開那對我來說異常神祕的面紗!
首先,是lambda表達式簡單的示例,通過這幾個小栗子可以加深對其概念的理解。
// 1. 不需要參數,返回值爲 5
() -> 5
// 2. 接收一個參數(數字類型),返回其2倍的值
x -> 2 * x
// 3. 接受2個參數(數字),並返回他們的差值
(x, y) -> x – y
// 4. 接收2個int型整數,返回他們的和
(int x, int y) -> x + y
// 5. 接受一個 string 對象,並在控制檯打印,不返回任何值(看起來像是返回void)
(String s) -> System.out.print(s)
以第3個例子,我們嘗試實現它,代碼如下:
package com.wjb.lambda;
/**
* 函數式接口:
* 1.只能有一個抽象方法
2.可以有靜態方法和默認方法,因爲這兩種方法都是已經實現的了
3.這個抽象方法一定不能跟Object類中的方法同名。
* @author Administrator
*
*/
@FunctionalInterface
interface Fun{
//只設置一個抽象方法 ------可以正常運行
abstract int spend(int remaining,int money);
//加入靜態方法和默認方法 ----可以正常運行
static void staticMethod() {
System.out.println("There is a static method!");
}
default void defaultMethod() {
System.out.println("There is a default method!");
}
//設置Object中可以重寫的抽象方法 ----編譯報錯
// abstract boolean equals(Object obj);
}
public class FunInterface{
public static void main(String[] args) {
Fun fun = (a,b) -> a-b;
System.out.println(fun.spend(100, 5));
}
}
這樣看來,lambda表達式真的很簡潔。
下面,我們看看lambda表達式的常用場景:
1. 列表迭代
對一個列表的每一個元素進行操作,不使用 Lambda 表達式時如下:
List<Integer> list = Arrays.asList(1,2,3,4,5);
for (Integer integer : list) {
System.out.println(list);
}
使用Lambda表達式的話是怎麼呢?
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.forEach((integer2) -> System.out.println(integer2));
一行就搞定了!但是需要注意的是,java 8提出的foreach方法其底層就是實現了Consumer接口的accept方法,所以可以用lambda表達式當做參數。
如果只需要調用單個函數對列表元素進行處理,那麼可以使用更加簡潔的 方法引用
代替 Lambda 表達式:
list.forEach(System.out::println);
這裏的方法引用屬於引申,感興趣的可以看這篇博客 Java 8 方法引用
下面列舉一些真實開發可能會遇到的場景,看看lambda表達式真的是神奇:
(1) 比如:從List<Object>對象列表中獲取對象單個字段組成的新List,
當我們要從List<User> userList中獲取所有用戶的Id列表並組成新的List要怎麼做,尋常方法可以實現但是過於複雜了
List<Long> userIdList = userList.stream.map(User::getUserId).collect(Collectors.toList());
Stream也是java 8提出的一個非常重要的特性,詳情可以看 Java 8 Stream 學習筆記
(2)修改List<User> userList中對象的某個字段值
List<User> userList;//獲取所有用戶的Id列表
userList.foreach(User -> User.setUserId(User.getUserId+1));
(3)修改List<User> userList中多個參數值
userList.foreach((x) -> {x.setUserId(0L); x.setUserName(""); x.setUserSex(0);})
(4) List過濾值
List<String> list
//過濾null值
list.stream.filter(x -> x != null).collect(Collectors.toList());
//過濾特定值(包括空字符串"")
list.stream.filter(x -> !"str".equals(x)).collect(Collectors.toList());
(5)逗號分隔的字符串轉List<Long>
List<Long> listIds = Arrays.asList(ids.split(",")).stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());
代碼功能是先將字符串切割成數組後轉列表,然後遍歷列表時去掉空格並轉成Long類型的元素,最後轉成新的列表。
(6) List轉Map(假設列表是List<FlowNodeTimeoutRuleUser> userList)
//1.List對象中兩個字段對應
Map<Long, String> userMap1 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, FlowNodeTimeoutRuleUser::getUserName));
//2.List對象中字段和對象本體對應
Map<Long, String> userMap2 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, x -> x));
//本體表達式可以用lambda表達式x->x 也可以使用接口Function.identity()
Map<Long, String> userMap3 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, Function.identity()));
//3. List對象中key字段重複導致錯誤,增加一個lambda表達式(key1, key2) -> key2,後者覆蓋前者解決key重複問題
Map<Long, String> userMap4 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, Function.identity(), (key1, key2) -> key2));
具體某個方法不懂的可以查看下源碼就知道了。
(7)統計List中重複數量:
List<Integer> list = Arrays.asList(1,1,2,5,2,3,4,5);
//統計List中重複數量
Map<Integer,Long> map = list.stream().collect(Collectors.groupingBy(x -> x,Collectors.counting()));
其中的lambda表達式x->x就是list元素本身。
(8)List去除重複值:
List.stream().distinct().collect(Collectors.toList());
(9)根據list對象屬性值去除重複值
List<Person> unique = persons.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))), ArrayList::new)
);
(10)List排序 按照從小到大排序
List<User> userList;
userList.sort((User u1, User u2) -> u1.getAge().compareTo(u2.getAge()));
2. 事件監聽
不使用 Lambda 表達式:
button.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
//handle the event
}
});
這裏是採用的匿名內部類的方式實現的。
使用 Lambda 表達式,需要編寫多條語句時用花括號包圍起來:
button.addActionListener(e -> {
//handle the event
});
3. Predicate 接口
java.util.function 包中的 Predicate 接口可以很方便地用於過濾。如果你需要對多個對象進行過濾並執行相同的處理邏輯,那麼可以將這些相同的操作封裝到 filter 方法中,由調用者提供過濾條件,以便重複使用。
不使用 Predicate 接口,對於每一個對象,都需要編寫過濾條件和處理邏輯:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<String> words = Arrays.asList("a", "ab", "abc");
numbers.forEach(x -> {
if (x % 2 == 0) {
//process logic
}
})
words.forEach(x -> {
if (x.length() > 1) {
//process logic
}
})
使用 Predicate 接口,將相同的處理邏輯封裝到 filter 方法中,重複調用:
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<String> words = Arrays.asList("a", "ab", "abc");
filter(numbers, x -> (int)x % 2 == 0);
filter(words, x -> ((String)x).length() > 1);
}
public static void filter(List list, Predicate condition) {
list.forEach(x -> {
if (condition.test(x)) {
//process logic
}
})
}
filter 方法也可寫成:
public static void filter(List list, Predicate condition) {
list.stream().filter(x -> condition.test(x)).forEach(x -> {
//process logic
})
}
4.Map 映射
使用 Stream 對象的 map 方法將原來的列表經由 Lambda 表達式映射爲另一個列表,並通過 collect 方法轉換回 List 類型:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> mapped = numbers.stream().map(x -> x * 2).collect(Collectors.toList());
mapped.forEach(System.out::println);
將列表的元素乘以2之後賦給一個新的列表,用Stream的方式實現,代碼量少了好多。
5. Reduce聚合
reduce 操作,就是通過二元運算對所有元素進行聚合,最終得到一個結果。例如使用加法對列表進行聚合,就是將列表中所有元素累加,得到總和。
因此,我們可以爲 reduce 提供一個接收兩個參數的 Lambda 表達式,該表達式就相當於一個二元運算:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce((x, y) -> x + y).get();
System.out.println(sum);
都是用Stream的方式實現的!
6. 代替Runnable
這個也是特別見的場景,下面以創建線程爲例,尋常使用 Runnable 類的代碼如下:
Runnable r = new Runnable() {
@Override
public void run() {
//to do something
}
};
Thread t = new Thread(r);
t.start();
使用 Lambda 表達式:
Runnable r = () -> {
//to do something
};
Thread t = new Thread(r);
t.start();
或者使用更加緊湊的形式:
new Thread(()->System.out.println("run thread...")).start();
參考文獻