Java8 Stream常用操作


Java8提供了强大的Stream API,对于操作集合、数组等,非常方便也非常优雅。本文主要整理下平时常用的一些操作。

1. List

List操作是Stream中最常见的操作,下面将介绍一些常用的List操作。

1.1. 去重

1
2
3
4
5
6
7
8
9
public class StreamTest {
@Test
public void test_listDistinct() {
List<String> oldList = Arrays.asList("a", "b", "a", "c");
List<String> newList = oldList.stream().distinct().collect(Collectors.toList());
System.out.println("oldList: " + oldList);
System.out.println("newList: " + newList);
}
}

上述测试代码输出如下:

1
2
oldList: [a, b, a, c]
newList: [a, b, c]

去重的核心代码:

1
oldList.stream().distinct().collect(Collectors.toList())

1.2. 数组转List

1
2
3
4
5
6
7
// int数组转List
int[] ints = {1, 2, 3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

// String数组转List
String[] arrays = new String[]{"a", "b", "c"};
List<String> listStrings = Stream.of(arrays).collector(Collectors.toList());

1.3. List转数组

1
String[] ss = listStrings.stream().toArray(String[]::new);

1.4. List转Map

1.4.1. 方法引用

常见的List转Map,用的是方法引用,分别生成key、value
数据集见下节

1
2
3
4
5
6
7
Map<String, Person> cityMap = persons.stream()
.collect(Collectors.toMap(
Person::getCity,
Function.identity(),
(v1, v2) -> v2
));
System.out.println("city map:" + cityMap);

以上输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
city map: {
basailuona = Person {
name = 'meixiaoxi',
job = '足球员',
city = 'basailuona'
},
beijing = Person {
name = 'meidaxi',
job = '教师',
city = 'beijing'
},
huangshi = Person {
name = 'wxweven',
job = '程序员',
city = 'huangshi'
}
}

1.4.2. lambda

有时候,list转map,没办法直接用方法引用,也可以用lambda

1
2
3
4
5
6
.collect(Collectors.toMap(
p -> p.getCity(), // 生成key的lambda
p -> p.getName().length(), // 生成value的lambda
(v1, v2) -> v2
)
);

2. 分组

假设我们有如下数据:

姓名 职业 城市
wxweven 程序员 huangshi
even 教师 beijing
meixiaoxi 足球员 basailuona
meidaxi 教师 beijing

每条记录对应的POJO如下:

1
2
3
4
5
public class Person {
private String name;
private String job;
private String city;
}

2.1. 分组聚合

如果想按照城市分组,即转化为一个map:

key为city,value为该city对应的Person对象集合

代码如下:

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
@Test
public void test_listGroup() {
List<Person> persons = listPersons();

// 按照city来分组
Map<String, List<Person>> personMap = persons.stream()
.collect(Collectors.groupingBy(Person::getCity));
System.out.println("按城市分组后:" + personMap);
}

private List<Person> listPersons() {
List<Person> persons = new ArrayList<>();
Person p = new Person("wxweven", "程序员", "huangshi");
persons.add(p);

p = new Person("even", "教师", "beijing");
persons.add(p);

p = new Person("meixiaoxi", "足球员", "basailuona");
persons.add(p);

p = new Person("meidaxi", "教师", "beijing");
persons.add(p);

return persons;
}

上述代码输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
按城市分组后:{
basailuona = [Person {
name = 'meixiaoxi',
job = '足球员',
city = 'basailuona'
}],
beijing = [Person {
name = 'even',
job = '教师',
city = 'beijing'
},
Person {
name = 'meidaxi',
job = '教师',
city = 'beijing'
}],
huangshi = [Person {
name = 'wxweven',
job = '程序员',
city = 'huangshi'
}]
}

可以看到,city为beijing的两个person被分配到了同一组,类似于MySQL中的groupBy的效果(不过MySQL中的groupBy只能保留一条数据)。

2.2. 分组后聚合计算

还是以上面的数据来举栗子,比如说要计算每个城市的人数,只需传递一个计数收集器给 groupingBy 收集器。第二个收集器的作用是在流分类的同一个组中对每个元素进行递归操作。代码如下:

1
2
3
Map<String, Long> nums = persons.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));
System.out.println("每个城市的人数:" + nums);

输出如下:

1
每个城市的人数:{basailuona=1, beijing=2, huangshi=1}

这个其实和MySQL中的语句是一样的功能:

1
select city, count(*) from Person group by city

除了counting之外,Stream也支持其他类型的聚合函数,如averagingInt(求平均值)、summingInt(求和)、maxBy(最大值)、minBy(最小值)等MySQL中支持的聚合函数。

2.3. 分组后汇总

我们来看Stack Overflow上的一个问题:

If the input was
[(1, “one”), (1, “eins”), (1, “uno”), (2, “two”), (3, “three”)]
the output would be
{ 1 = [“one”, “eins”, “uno”], 2 = [“two”], 3 = [“three”] }

1
2
3
4
5
tuples.stream()
.collect(Collectors.groupingBy(Tuple::getFirst,
Collectors.mapping(
Tuple::getSecond,
Collectors.toSet())));

3. Map转化

1
2
3
4
5
6
7
8
Map<Integer, List> maps = getMap();
Map<Integer, String> maps2 = maps.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue(),
(v1, v2) -> v2
));

4. flatMap

flatMap用户将几个小的list合并到一个大的list,即『打平』的意思,看下面的图:
QHA9Cq.png
假设我们有两个List:

1
2
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);

如果我们想把list1和list2合并成一个List,新的List值为:[1, 2, 3, 4, 5, 6], 那么可以用 flatMap 来实现。完整例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testFlatMap() {
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
List<Integer> list3 = Arrays.asList(7, 8, 9, 3);


List<Integer> newList = Stream.of(list1, list2, list3)
.flatMap(Collection::stream)
.distinct() // 去重
.collect(Collectors.toList());

System.out.println("newList=" + newList);
}

输出如下:

1
newList=[1, 2, 3, 4, 5, 6, 7, 8, 9]