0%

Java8 Stream的介绍

声明:本文使用JDK1.8

Java8提供了丰富的lambda的API。今天我们来讲讲关于其中的stream(流的操作)。对于List集合,有了stream的流操作,感觉如虎添翼。

生成一个List

1
2
3
4
5
6
7
String str = "1,2,3,4,10,11,9,66,222,12";
List<Integer> list = Stream.of(str.split(","))
.map(Integer::valueOf)
.filter(x-> !Objects.equals(x,3))
.sorted(Comparator.reverseOrder())
.limit(4)
.collect(Collectors.toList());

上面的代码Stream.of 为我们生成了一个List,但是我们需要的Integer,所以我们还需要使用map来转换类型。然后对于生成的列表,我们不想要其中的3,于是我们使用filter来过滤掉列表中的3。对于现在列表我们又想是倒序后的列表的前四条数据,于是我们在使用sorted来对列表来进行倒序后,再使用limit取前四条。顺便说一句,对于比较,推荐使用Objects.equals(a,b).
下面来简单的介绍下stream。

map

map:对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素
map方法是对列表里面的对象的转换,比如上面的map的功能是吧String转换成Integer。除了这样的转换,你也可以对对象进行你需要的操作。比如现在有一个对象ListDTO:

1
2
3
4
5
6
7
8
9
10
11
@Data
public class ListDTO{
public Integer id;
public String name;
public ListDTO() {
}
public ListDTO(Integer id, String name) {
this.id = id;
this.name = name;
}
}

现在这么一个List,你现在只想得到这个列表中ListDTO对象里面的id,现在你就可以这样操作:

1
List<Integer> idList = beans.stream().map(ListDTO::getId).collect(Collectors.toList());

如果你想要得到name的列表,也可以进行上面类似的操作。下面对List列表的元素先转换成大写,在用逗号把元素拼接起来。

1
2
3
4
// 将字符串换成大写并用逗号拼接起来
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);

输出:

1
USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

flatMap

类似于map,但是flatMap是对Stream之间的转换。

1
2
3
String[][] data = new String[][]{{"a", "b"}, {"c", "d"}, {"e", "f"}};
Stream<String[]> temp = Arrays.stream(data);
Stream<String[]> stream = temp.filter(x -> "a".equals(x.toString()));

count

count()是对列表的聚合,统计列表的数量。

1
Long count = list.stream().count();

filter

对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素

distinct

distinct是对stream里面的元素进行去重,有点类似于SQL里面的distinct去重

limit

对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素

stream 生成 map

上面我们介绍了stream里面的toList(),下面我们来介绍下如何使用stream直接生成map,而不是像以前一样,需要循环后再一个一个的put进map。虽然我们也可以第三方的jar生成Map,比如Google的某些jar,使用里面的Maps.uniqueIndex()也可以直接把List转换成Map。

toMap

利用Collectors里面的toMap对List列表转换Map。

1
2
3
4
5
6
7
// 这种toMap,当出现Key相同时,会出现 Duplicate key 问题
Map<Integer,String> beanMap = beans.stream()
.collect(Collectors.toMap(x->x.getId(), m->m.getName()));

//解决key冲突问题,使用新的value替换原来的value
Map<Integer, String> map = beans.stream()
.collect(Collectors.toMap(SimpleDTO::getId, SimpleDTO::getName, (v1, v2) -> v2));

前者的toMap使用的是 throwingMerger,在出现key相同时,抛出异常;后者的toMap使用的是mergeFunction,如示例中,如果出现key相同的元素,新的key能覆盖旧的。

groupingBy

利用Collectors里面的groupingBy,可以对列表进行分组,总和和排序。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public static void main(String[] args) {

List<String> items = Arrays.asList("Apple", "Apple", "orange",
"Apple", "orange", "banana", "papaya");

Map<String, Long> result = items
.stream()
.collect(Collectors
.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(result);

//排序
Map<String, Long> finalMap = new LinkedHashMap<>();
//sort a map and add to finalMap
result.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue()
.reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));

System.out.println(finalMap);
//数据统计
List<SimpleDTO> beans = BeanData.getBeanDataList();
Map<Integer, Long> countMap = beans
.stream().collect(
Collectors.groupingBy(SimpleDTO::getId, Collectors.counting()));
System.out.println(countMap);

Map<Integer, List<SimpleDTO>> listMap = beans.stream().collect(
Collectors.groupingBy(SimpleDTO::getId));
System.out.println(JSON.toJSON(listMap));

//Map 的value值自行聚合
Map<Integer, Set<String>> setMap = beans.stream().collect(
Collectors.groupingBy(SimpleDTO::getId, Collectors.mapping(SimpleDTO::getName, Collectors.toSet()))
);
System.out.println(JSON.toJSON(setMap));

//Map的value重新聚合一个新的对象
Map<Integer, List<UserDTO>> collectMap = beans.stream()
.collect(Collectors.groupingBy(SimpleDTO::getId,
Collectors.collectingAndThen(Collectors.toList(), input -> input.stream()
.map(m -> {
UserDTO user = new UserDTO();
user.setId(m.getId());
user.setUsername(m.getName());
return user;
}).collect(Collectors.toList())
)));
System.out.println(JSON.toJSON(collectMap));

SimpleDTO dto1 = new SimpleDTO(1, "Jack", "1");
SimpleDTO dto2 = new SimpleDTO(2, "James", "2");
SimpleDTO dto3 = new SimpleDTO(3, "Hangzhou", "3");
SimpleDTO dto4 = new SimpleDTO(3, "Hangzhou", "4");
List<SimpleDTO> list = new ArrayList<>();
list.add(dto1);
list.add(dto2);
list.add(dto3);
list.add(dto4);

//自行拼接KEY
Map<String, List<SimpleDTO>> mapKey = list.stream()
.collect(Collectors.groupingBy(e -> fetchGroupKey(e)));
System.out.println(mapKey);

}

private static String fetchGroupKey(SimpleDTO dto) {
return dto.getId() + "_" + dto.getName();
}

总和

1
2
3
4
5
List<String> items = Arrays.asList("Apple", "Apple", "orange",
"Apple", "orange", "banana", "papaya");
Map<String,Long> result = items.stream()
.collect(Collectors
.groupingBy(Function.identity(),Collectors.counting()));

输出结果:

1
{papaya=1, banana=1, orange=2, Apple=3}

排序

1
2
3
4
5
6
Map<String, Long> finalMap = new LinkedHashMap<>();
//Sort a map and add to finalMap
result.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue()
.reversed()).forEachOrdered(e -> finalMap.put(e.getKey(), e.getValue()));
System.out.println(finalMap);

分组

现在有数据

1
2
3
4
5
6
7
public static List<ListDTO> getBeanDataList(){
List<ListDTO> listDTOS = Arrays.asList(
new ListDTO(1,"孙博"), new ListDTO(1,"二代"),new ListDTO(1,"孙博"),
new ListDTO(2,"戴硕"),new ListDTO(2,"戴硕"),new ListDTO(2,"赛克"),
new ListDTO(3,"二代"),new ListDTO(3,"路痴"),new ListDTO(3,"路痴"),
new ListDTO(4,"赛克"),new ListDTO(4,"二代"),new ListDTO(4,"路痴")
);

下面更加其中的ID作为Map的key,对上面的List进行Map.

1
2
Map<Integer,List<ListDTO>> listMap = beans.stream().collect(
Collectors.groupingBy(ListDTO::getId));

把对象中的name取出并塞到一个Set中

1
2
3
Map<Integer,Set<String>> setMap = beans.stream().collect(
Collectors.groupingBy(ListDTO::getId,Collectors.mapping(ListDTO::getName,Collectors.toSet()))
);

其他

现在需要对上面的集合,根据ID,对数据进行统计。

1
2
3
4
Map<Integer,Long> countMap = beans
.stream().collect(
Collectors.groupingBy(ListDTO::getId,Collectors.counting())
);

Stream 和 parallelStream

Stream 是普通的对流操作,而 parallelStream 是对集合进行并发操作,看下面的对两者的简单比较:

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
public static void main(String[] args) {
List<SimpleDTO> list = new ArrayList<>();
for (int i = 1; i < 100000; i++) {
SimpleDTO dto = new SimpleDTO();
dto.setId(i);
dto.setName("测试员" + i + "号");
dto.setContent("Stream 测试普通和并行的效率");
list.add(dto);
}

List<SimpleDTO> testList = list;

Long stattTime = System.currentTimeMillis();
List<String> list1 = list.stream()
.filter(x -> x.getId() > 1000)
.map(SimpleDTO::getName)
.collect(Collectors.toList());
Long endTime = System.currentTimeMillis();

System.out.println("stream 耗时:" + (endTime - stattTime) + "ms");

List<String> list2 = testList.parallelStream()
.filter(x -> x.getId() > 1000)
.map(SimpleDTO::getName)
.collect(Collectors.toList());
Long endTime1 = System.currentTimeMillis();

System.out.println("parallelStream 耗时:" + (endTime1 - endTime) + "ms");
}

结果:

1
2
stream 耗时:77ms
parallelStream 耗时:19ms

从最终的耗时可以看出,对于 parallelStream 处理大量数据的时候,效率还是有很大的提升的。

客官,赏一杯coffee嘛~~~~