使用Java Stream,提取集合中的某一列/按條件過濾集合/求和/最大值/最小值/平均值

申城異鄉人發表於2020-09-17

不得不說,使用Java Stream操作集合實在是太好用了,不過最近在觀察生產環境錯誤日誌時,發現偶爾會出現以下2個異常:

  1. java.lang.NullPointerException
  2. java.util.NoSuchElementException

因此本篇部落格總結下使用Java Stream的部分場景以及如何避免上述的2個異常:

  1. 提取集合中的某一列(普通提取、去重)
  2. 按條件過濾集合
  3. 求和
  4. 最大值/最小值/平均值

1. 資料準備

首先定義下Friend類:

package com.zwwhnly.springbootaction.model;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class Friend {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    /**
     * 身高
     */
    private Long height;

    /**
     * 所在城市
     */
    private String city;

    /**
     * 體重
     */
    private BigDecimal weight;

    public Friend(String name, Integer age, Long height, String city, BigDecimal weight) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.city = city;
        this.weight = weight;
    }
}

然後初始化以下資料供後面使用:

public static List<Friend> getFriendList() {
    List<Friend> friendList = new ArrayList<>();
    friendList.add(new Friend("小周", 28, 175L, "鄭州", new BigDecimal("101.5")));
    friendList.add(new Friend("小吳", 28, 170L, "洛陽", new BigDecimal("111.5")));
    friendList.add(new Friend("小鄭", 29, 176L, "鄭州", new BigDecimal("121.5")));
    friendList.add(new Friend("小王", 29, 180L, "北京", new BigDecimal("130")));
    friendList.add(new Friend("小趙", 27, 178L, "蘇州", new BigDecimal("140")));
    friendList.add(new Friend("小錢", null, null, "杭州", new BigDecimal("150")));

    return friendList;
}

2. 提取集合中的某一列

2.1 普通提取

比如,我們需要提取出所有朋友的姓名,可以使用Stream的map()方法,實現程式碼如下所示:

List<Friend> friendList = getFriendList();
List<String> nameList = friendList.stream().map(Friend::getName).collect(Collectors.toList());

nameList.forEach(name -> System.out.println(name));

輸出結果:

小周

小吳

小鄭

小王

小趙

2.2 提取後去重

比如,我們需要提取出所有朋友的年齡,但是需要去重,可以使用Stream的distinct()方法,實現程式碼如下所示:

List<Friend> friendList = getFriendList();
List<Integer> ageList = friendList.stream().map(Friend::getAge).distinct().collect(Collectors.toList());

ageList.forEach(age -> System.out.println(age));

輸出結果:

28

29

27

3. 按條件過濾集合

比如,我們需要獲取所有朋友中年齡在29歲以下,並且身高在170以上的朋友,可以呼叫filter方法,實現程式碼如下所示:

List<Friend> friendList = getFriendList();

List<Friend> youngPeople = friendList.stream()
        .filter(friend -> friend.getAge() != null && friend.getAge() < 29 &&
                friend.getHeight() != null && friend.getHeight() > 170L)
        .collect(Collectors.toList());
System.out.println(youngPeople);

輸出結果:

Friend(name=小周, age=28, height=175, city=鄭州, weight=101.5)

Friend(name=小趙, age=27, height=178, city=蘇州, weight=140)

4. 求和

4.1 Integer,Long,Double

比如,我們需要計算出所有朋友的年齡之和,可以呼叫mapToInt方法,實現程式碼如下所示:

List<Friend> friendList = getFriendList();

int ageSum = friendList.stream().filter(friend -> friend.getAge() != null).mapToInt(Friend::getAge).sum();
System.out.println(ageSum);

輸出結果:

141

注意事項:

因為我們的age欄位定義的是包裝型別Integer,但求和之後的返回型別為基本型別int,所以在呼叫mapToInt方法之前,一定要過濾掉年齡為null的資料,否則分分鐘拋異常。

比如,我們新增一條年齡為null的資料:

friendList.add(new Friend("小錢",null,178,"杭州"));

然後,我們不過濾null資料,直接呼叫mapToInt方法,就會丟擲java.lang.NullPointerException異常:

List<Friend> friendList = getFriendList();

int ageSum = friendList.stream().mapToInt(Friend::getAge).sum();
System.out.println(ageSum);

如果欄位型別是Long或者Double,可以呼叫相應的mapToDoublemapToLong,如下所示:

4.2 BigDecimal

和Integer、Long、Double型別不同,如果欄位型別是BigDecimal,求和的話需要呼叫reduce方法,使用方法如下所示:

List<Friend> friendList = getFriendList();

BigDecimal weightSum = friendList.stream()
        .filter(friend -> friend.getWeight() != null)
        .map(Friend::getWeight)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(weightSum);

輸出結果:

754.5

注意事項:

為避免java.lang.NullPointerException異常,上面程式碼中的.filter(friend -> friend.getWeight() != null)也要記得加。

5. 最大值/最小值/平均值

5.1 Integer,Long,Double

比如,我們需要獲取所有朋友中身高的最大值,實現程式碼如下所示:

List<Friend> friendList = getFriendList();

long heightMax = friendList.stream()
        .filter(friend -> friend.getHeight() != null)
        .mapToLong(Friend::getHeight)
        .max().orElse(0);
System.out.println(heightMax);

輸出結果:

180

注意事項:

因為max()方法的返回值是OptionalLong型別,所以我們需要繼續呼叫orElse()方法設定個預設值,這裡不要直接使用getAsLong()方法,因為當集合為空時,會丟擲你肯定遇到過的java.util.NoSuchElementException異常:

long heightMax = friendList.stream()
        .filter(friend -> friend.getHeight() != null)
        .mapToLong(Friend::getHeight)
        .max().getAsLong();

orElse()原始碼如下所示:

public long orElse(long other) {
    return isPresent ? value : other;
}

getAsLong()原始碼如下所示:

public long getAsLong() {
    if (!isPresent) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

類似地,獲取最小值的程式碼如下所示:

List<Friend> friendList = getFriendList();

long heightMin = friendList.stream()
        .filter(friend -> friend.getHeight() != null)
        .mapToLong(Friend::getHeight)
        .min().orElse(0);
System.out.println(heightMin);

獲取平均值的程式碼如下所示:

List<Friend> friendList = getFriendList();

double heightAverage = friendList.stream()
    .filter(friend -> friend.getHeight() != null)
    .mapToLong(Friend::getHeight)
    .average().orElse(0D);
System.out.println(heightAverage);

5.2 BigDecimal

比如,我們需要獲取所有朋友中體重的最大值,實現程式碼如下所示:

List<Friend> friendList = getFriendList();

BigDecimal weightMax = friendList.stream()
        .filter(friend -> friend.getWeight() != null)
        .map(Friend::getWeight)
        .max(BigDecimal::compareTo)
        .orElse(BigDecimal.ZERO);
System.out.println(weightMax);

輸出結果:

150

注意事項:

1)為避免出現java.lang.NullPointerException異常,注意過濾體重為null的資料

2)因為max()方法的返回值為Optional型別,所以我們需要繼續呼叫orElse()方法設定個預設值,這裡不要直接使用get()方法,因為當集合為空時,會丟擲你肯定遇到過的java.util.NoSuchElementException異常:

BigDecimal weightMax = friendList.stream()
        .filter(friend -> friend.getWeight() != null)
        .map(Friend::getWeight)
        .max(BigDecimal::compareTo)
        .get();

get()方法原始碼如下所示:

public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

類似地,獲取最小值的程式碼如下所示:

List<Friend> friendList = getFriendList();

BigDecimal weightMax = friendList.stream()
        .filter(friend -> friend.getWeight() != null)
        .map(Friend::getWeight)
        .min(BigDecimal::compareTo)
        .orElse(BigDecimal.ZERO);
System.out.println(weightMax);

6. 總結

使用Java Stream操作集合非常便利,但還是容易踩一些坑,比如文中提到的java.lang.NullPointerException異常和java.util.NoSuchElementException異常,所以使用時要多多注意,能不踩坑就不踩坑,就算踩坑,也別多次踩同一個坑。


相關文章