Java 8 Lambda學習筆記(一)

一直感覺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();

參考文獻

Java Lambda 表達式的常見應用場景

Java中Lambda表達式的使用

工作中經常用到的lambda表達式示例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章