新特性05| Stream

简介

Stream represents a sequence of objects from a source, which supports aggregate operations. Following are the characteristics of a Stream −

  • Sequence of elements − A stream provides a set of elements of specific type in a sequential manner. A stream gets/computes elements on demand. It never stores the elements.
  • Source − Stream takes Collections, Arrays, or I/O resources as input source.
  • Aggregate operations − Stream supports aggregate operations like filter, map, limit, reduce, find, match, and so on.
  • Pipelining − Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target. collect() method is a terminal operation which is normally present at the end of the pipelining operation to mark the end of the stream.
  • Automatic iterations − Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.

(译文)Stream为输入源提供了对象序列,它支持聚合操作。以下是Stream的特性:

  • 元素序列——元素是特定类型的对象,Stream按需获取/计算元素,它不存储元素;
  • 数据源——Stream将集合、数组或输入输出资源作为输入源;
  • 聚合操作——Stream支持filter、map、limit、reduce、find、match等聚合操作;
  • Pipelining——大多Stream操作返回Stream本身,这样多个操作可以串联成一个管道,如同流式风格。这些操作被称为中间操作,这些方法获取输入、进行处理、然后将输出返回给目标。collect()方法是最终操作,一般在流水线的末尾出现标志着Stream的结束;(类似的最终操作还有:forEach、reduce)
  • 内部迭代——与需要显式迭代的集合相比,流操作在内部对提供的源元素执行迭代(通过访问者模式完成)。

说明

Stream是Java8引入的一个抽象层,使用它可以用类似于SQL语句声明式地处理数据。比如可以看下方sql语句。

SELECT max(salary), employee_id, employee_name FROM Employee

上面的sql语句在用户端并没有执行任何计算操作的情况下,自动返回了Employee表中salary的最大值max(salay)。但如果使用Java的集合类,开发者须使用循环并且要多次比对才能给出最大值。另一个需要考虑的就是效率问题,由于现在大多都是多核处理器,Java开发人员用并行代码来处理时容易出错。

为了解决上述问题,Java8引入了Stream概念,它让开发人员可以声明式地处理数据并且可以在不写任何特定代码的情况下利用多核处理器。

形象化展示

1
2
3
+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+

用Java表示上述流程

1
2
3
4
5
6
7
// 这里widgets为输入源
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();

生成流

在Java8中, 集合接口有两个方法来生成流:

  1. stream() − 为集合创建串行流。
  2. parallelStream() − 为集合创建并行流。

举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class GenerateStream {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered1 = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
filtered1.forEach(System.out::println);

System.out.println("________________\n");

List<String> filtered2 = strings.parallelStream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
filtered2.forEach(System.out::println);

}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
abc
bc
efg
abcd
jkl
________________

abc
bc
efg
abcd
jkl

Steam常用方法

方法名 功能
collect 利用Collectors接口实现归约操作
filter 通过设置条件过滤出元素
forEach 迭代流中的每个数据
limit 获取指定数量的流
map 映射每个元素到对应的结果
sorted 对流进行排序

Stream完整示例

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import java.util.*;
import java.util.stream.Collectors;

public class Java8Tester {
public static void main(String[] args) {
System.out.println("使用 Java 7: ");

// 计算空字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
long count = getCountEmptyStringUsingJava7(strings);
System.out.println("空字符数量为: " + count);

count = getCountLength3UsingJava7(strings);
System.out.println("字符串长度为 3 的数量为: " + count);

// 删除空字符串
List<String> filtered = deleteEmptyStringsUsingJava7(strings);
System.out.println("筛选后的列表: " + filtered);

// 删除空字符串,并使用逗号把它们合并起来
String mergedString = getMergedStringUsingJava7(strings);
System.out.println("合并字符串: " + mergedString);

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取列表元素平方数
List<Integer> squaresList = getSquares(numbers);
System.out.println("平方数列表: " + squaresList);

List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
System.out.println("列表中最大的数 : " + getMax(integers));
System.out.println("列表中最小的数 : " + getMin(integers));
System.out.println("所有数之和 : " + getSum(integers));
System.out.println("平均数 : " + getAverage(integers));

System.out.println("随机数: ");
// 输出10个随机数
Random random = new Random();
for (int i = 0; i < 10; i++) {
System.out.println(random.nextInt());
}

System.out.println("-------------------------------------");

System.out.println("使用 Java 8: ");
System.out.println("列表: " + strings);

count = strings.stream().filter(String::isEmpty).count();
System.out.println("空字符串数量为: " + count);

count = strings.stream().filter(string -> string.length() == 3).count();
System.out.println("字符串长度为 3 的数量为: " + count);

filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选后的列表: " + filtered);

mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("平方数列表: " + squaresList);

System.out.println("列表: " + integers);
IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

System.out.println("随机数: ");
random.ints().limit(10).sorted().forEach(System.out::println);

// 并行处理
count = strings.parallelStream().filter(String::isEmpty).count();
// 输出结果:空字符串的数量为: 2
System.out.println("\n空字符串的数量为: " + count);
}

private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;

for (String string : strings) {

if (string.isEmpty()) {
count++;
}
}
return count;
}

private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;

for (String string : strings) {

if (string.length() == 3) {
count++;
}
}
return count;
}

private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<>();

for (String string : strings) {

if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}

private static String getMergedStringUsingJava7(List<String> strings) {
StringBuilder stringBuilder = new StringBuilder();

for (String string : strings) {

if (!string.isEmpty()) {
stringBuilder.append(string);
stringBuilder.append(", ");
}
}
String mergedString = stringBuilder.toString();
return mergedString.substring(0, mergedString.length() - 2);
}

private static List<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<>();

for (Integer number : numbers) {
Integer square = number * number;

// 去重
if (!squaresList.contains(square)) {
squaresList.add(square);
}
}
return squaresList;
}

private static int getMax(List<Integer> numbers) {
int max = numbers.get(0);

for (int i = 1; i < numbers.size(); i++) {

Integer number = numbers.get(i);

max = number > max ? number : max;
}
return max;
}

private static int getMin(List<Integer> numbers) {
int min = numbers.get(0);

for (int i = 1; i < numbers.size(); i++) {
Integer number = numbers.get(i);

min = number < min ? number : min;
}
return min;
}

private static int getSum(List numbers) {
int sum = (int) (numbers.get(0));

for (int i = 1; i < numbers.size(); i++) {
sum += (int) numbers.get(i);
}
return sum;
}

private static double getAverage(List<Integer> numbers) {
return 1.0*getSum(numbers) / numbers.size();
}
}

Java7和Java8输出对比

Java7 Java8
列表: [abc, , bc, efg, abcd, , jkl] 列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2 空字符串数量为: 2
字符串长度为 3 的数量为: 3 字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl] 筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl 合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25] 平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19] 列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19 列表中最大的数 : 19
列表中最小的数 : 1 列表中最小的数 : 1
所有数之和 : 85 所有数之和 : 85
平均数 : 9.444444444444445 平均数 : 9.444444444444445
随机数: 随机数:
1284026997 -1374685114
-1178762425 -1033099952
-1879259729 -885959722
-29731464 -401309484
1555365798 111924303
-1011945483 124136966
235379193 1033295745
-1233162879 1297745871
-704470337 1322164906
1219156879 2111470129

从上表可以看出Java8输出结果与Java7结果相同,但使用新特性的Java8的代码更加简洁明了!


更多详细内容参见官方文档

码哥 wechat
欢迎关注个人订阅号:「码上行动GO」