如何使用 Java Stream ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 先来几个常用的:

// 取Double的List聚合中的最大值
Double max = taxList.stream().max(Comparator.comparing(Double::doubleValue)).get();
usersList.stream().map(Student::getSalary).max(BigDecimal::compareTo).get()

// 拿对象中某个字段比较最大值
ZfastCustomerOrder info = zfastCustomerOrderList.stream().max(Comparator.comparing(ZfastCustomerOrder::getTaxRate)).get();
tbypkcListF.add(kclist.stream().max(Comparator.comparing(TBYPKC::getDYXQ)).get());

// 根据门诊号分组
Map<String, List<TBCFHZ>> map = cfhzlist.stream().collect(Collectors.groupingBy(TBCFHZ::getCMZH));

// 获取List中某一属性list
List<String> cfhlist = mapItem.getValue().stream().map(TBCFHZ::getCCFH).collect(Collectors.toList());

提取集合中的某一列/按条件过滤集合/求和/最大值/最小值/平均值。

不得不说,使用Java Stream操作集合实在是太好用了,不过最近在观察生产环境错误日志时,发现偶尔会出现以下2个异常:

  • java.lang.NullPointerException
  • java.util.NoSuchElementException

因此本篇博客总结下使用Java Stream的部分场景以及如何避免上述的2个异常:

  • 提取集合中的某一列(普通提取、去重)
  • 按条件过滤集合
  • 求和
  • 最大值/最小值/平均值

数据准备

  1. 首先定义下Friend类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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;
}
}
  1. 初始化以下数据供后面使用:
1
2
3
4
5
6
7
8
9
10
11
12
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;
}

普通提取

比如,我们需要提取出所有朋友的姓名,可以使用Stream的map()方法,实现代码如下所示:

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

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

输出结果:
小周
小吴
小郑
小王
小赵

提取后去重

比如,我们需要提取出所有朋友的年龄,但是需要去重,可以使用Stream的distinct()方法,实现代码如下所示:

1
2
3
4
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

按条件过滤集合

比如,我们需要获取年龄在29岁以下,并且身高在170以上的朋友,可以调用filter方法,实现代码如下所示:

1
2
3
4
5
6
7
8
9

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)

求和

Integer,Long,Double

比如,我们需要计算出所有朋友的年龄之和,可以调用mapToInt方法,实现代码如下所示:

List 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异常:

1
2
3
4
List<Friend> friendList = getFriendList();

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

如果字段类型是Long或者Double,可以调用相应的mapToDouble、mapToLong,如下所示:

BigDecimal

和Integer、Long、Double类型不同,如果字段类型是BigDecimal,求和的话需要调用reduce方法,使用方法如下所示:

1
2
3
4
5
6
7
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)也要记得加。

最大值/最小值/平均值

Integer,Long,Double

获取身高最大值:

1
2
3
4
5
6
7
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异常:

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

orElse()源码如下所示:

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

getAsLong()源码如下所示:

1
2
3
4
5
6
public long getAsLong() {
if (!isPresent) {
throw new NoSuchElementException("No value present");
}
return value;
}

获取最小值:

1
2
3
4
5
6
7
List<Friend> friendList = getFriendList();

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

获取平均值:

1
2
3
4
5
6
7
List<Friend> friendList = getFriendList();

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

BigDecimal

比如,我们需要获取所有朋友中体重的最大值,实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
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异常:

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

get()方法源码如下所示:

1
2
3
4
5
6
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

获取最小值的代码重写:

1
2
3
4
5
6
7
8
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);

总结

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

实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 过滤 根据list中对象的属性值, 找该对象在list中的索引位置 
// 可用于根据库存编码或药品编码找对应库存list的某库存对象 !
@Test
public void test05(){
List<Student> students = new ArrayList<>();
students.add(new Student(1, "小王", "天府大道一街", new BigDecimal("2100"), new BigDecimal("5000")));
students.add(new Student(2, "小王", "天府大道二街", new BigDecimal("2500"), new BigDecimal("5000")));
students.add(new Student(3, "小红", "天府大道三街", new BigDecimal("2600"), new BigDecimal("5000")));
students.add(new Student(4, "小明", "天府大道四街", new BigDecimal("4000"), new BigDecimal("5000")));
students.add(new Student(5, "小王", "天府大道五街", new BigDecimal("3000"), new BigDecimal("5000")));

String name = "小王";
Integer id = 2;

System.out.println(students.size());
for (int i = 0; i < students.size(); i++) {
System.out.println(students.get(i).toString() + " 索引值:" + i);
}

OptionalInt index = IntStream.range(0, students.size())
//根据姓名和id找需要的对象
.filter(i -> students.get(i).getName().equals(name)&&students.get(i).getId()==(id))
.findFirst();
//返回索引位置
System.out.println(index.getAsInt());

}

// 排序 根据工资排序
// 可用于根据参数给库存对象排序, 比如临近有效期先扣库存 !
@Test
public void test07(){
List<Student> students = new ArrayList<>();
students.add(new Student(1, "小王", "天府大道一街", new BigDecimal("2100"), new BigDecimal("5000")));
students.add(new Student(2, "小王", "天府大道二街", new BigDecimal("2500"), new BigDecimal("5000")));
students.add(new Student(3, "小红", "天府大道三街", new BigDecimal("2600"), new BigDecimal("5000")));
students.add(new Student(4, "小明", "天府大道四街", new BigDecimal("4000"), new BigDecimal("5000")));
students.add(new Student(5, "小王", "天府大道五街", new BigDecimal("3000"), new BigDecimal("5000")));

students.stream().sorted(
Comparator.comparing(Student::getSalary)
).collect(Collectors.toList()).forEach( student -> System.out.println(student.toString()));
}