public class Main {
public static void main(String[] args) {
Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
Stream<String> stream2 = List.of("X", "Y", "Z").stream();
stream1.forEach(System.out::println);
stream2.forEach(System.out::println);
}
}
Stream.map()
是Stream
最常用的一个转换方法,它把一个Stream
转换为另一个Stream
。
public class Main {
public static void main(String[] args) {
List.of(" Apple ", " pear ", " ORANGE", " BaNaNa ")
.stream()
.map(String::trim) // 去空格
.map(String::toLowerCase) // 变小写
.forEach(System.out::println); // 打印
}
}
Stream.filter()
是Stream
的另一个常用转换方法。
所谓filter()
操作,就是对一个Stream
的所有元素一一进行测试,不满足条件的就被“滤掉”了,剩下的满足条件的元素就构成了一个新的Stream
。
public class Main {
public static void main(String[] args) {
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(n -> n % 2 != 0)
.forEach(System.out::println);
}
}
map()
和filter()
都是Stream
的转换方法,而Stream.reduce()
则是Stream
的一个聚合方法,它可以把一个Stream
的所有元素按照聚合函数聚合成一个结果。
public class Main {
public static void main(String[] args) {
//0为初始变量 acc为0,n为1,2,3...
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);
System.out.println(sum); // 45
}
}
除了可以对数值进行累积计算外,灵活运用reduce()
也可以对Java对象进行操作。下面的代码演示了如何将配置文件的每一行配置通过map()
和reduce()
操作聚合成一个Map
:
public class Main {
public static void main(String[] args) {
// 按行读取配置文件:
List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
Map<String, String> map = props.stream()
// 把k=v转换为Map[k]=v:
.map(kv -> {
String[] ss = kv.split("\\=", 2);
return Map.of(ss[0], ss[1]);
})
// 把所有Map聚合到一个Map:
.reduce(new HashMap<String, String>(), (m, kv) -> {
m.putAll(kv);
return m;
});
// 打印结果:
map.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
}
}
reduce()
只是一种聚合操作,如果我们希望把Stream
的元素保存到集合,例如List
,因为List
的元素是确定的Java对象,因此,把Stream
变为List
不是一个转换操作,而是一个聚合操作,它会强制Stream
输出每个元素。
下面的代码演示了如何将一组String
先过滤掉空字符串,然后把非空字符串保存到List
中:
public class Main {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Apple", "", null, "Pear", " ", "Orange");
List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());
System.out.println(list);
}
}
把Stream的元素输出为数组和输出为List类似,我们只需要调用toArray()
方法,并传入数组的“构造方法”:
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
如果我们要把Stream的元素收集到Map中,就稍微麻烦一点。因为对于每个元素,添加到Map时需要key和value,因此,我们要指定两个映射函数,分别把元素映射为key和value:
public class Main {
public static void main(String[] args) {
Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
Map<String, String> map = stream
.collect(Collectors.toMap(
// 把元素s映射为key:
s -> s.substring(0, s.indexOf(':')),
// 把元素s映射为value:
s -> s.substring(s.indexOf(':') + 1)));
System.out.println(map);
}
}
Stream
还有一个强大的分组功能,可以按组输出。我们看下面的例子:
public class Main {
public static void main(String[] args) {
List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
Map<String, List<String>> groups = list.stream()
.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
System.out.println(groups);
}
}
分组输出使用Collectors.groupingBy()
,它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1)
,表示只要首字母相同的String
分到一组,第二个是分组的value,这里直接使用Collectors.toList()
,表示输出为List
,上述代码运行结果如下:
{
A=[Apple, Avocado, Apricots],
B=[Banana, Blackberry],
C=[Coconut, Cherry]
}
对Stream
的元素进行排序十分简单,只需调用sorted()
方法:
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(Arrays.asList("Orange", "apple", "Banana"))
.stream()
.sorted(String::compareToIgnoreCase) //转换为小写
.collect(Collectors.toList());
System.out.println(list);
}
}
对一个Stream
的元素进行去重,没必要先转换为Set
,可以直接用distinct()
:
List.of("A", "B", "A", "C", "B", "D")
.stream()
.distinct()
.collect(Collectors.toList()); // [A, B, C, D]
截取操作常用于把一个无限的Stream
转换成有限的Stream
,skip()
用于跳过当前Stream
的前N个元素,limit()
用于截取当前Stream
最多前N个元素:
List.of("A", "B", "C", "D", "E", "F")
.stream()
.skip(2) // 跳过A, B
.limit(3) // 截取C, D, E
.collect(Collectors.toList()); // [C, D, E]
将两个Stream
合并为一个Stream
可以使用Stream
的静态方法concat()
:
Stream s1 = List.of("A", "B", "C").stream();
Stream s2 = List.of("D", "E").stream();
// 合并:
Stream s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]
通常情况下,对Stream
的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream
的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通Stream
转换为可以并行处理的Stream
非常简单,只需要用parallel()
进行转换:
Stream<String> s = ...
String[] result = s.parallel() // 变成一个可以并行处理的Stream
.sorted() // 可以进行并行排序
.toArray(String[]::new);
除了reduce()
和collect()
外,Stream
还有一些常用的聚合方法:
count()
:用于返回元素个数;max(Comparator cp)
:找出最大元素;min(Comparator cp)
:找出最小元素。针对IntStream
、LongStream
和DoubleStream
,还额外提供了以下聚合方法:
sum()
:对所有元素求和;average()
:对所有元素求平均数。还有一些方法,用来测试Stream
的元素是否满足以下条件:
boolean allMatch(Predicate)
:测试是否所有元素均满足测试条件;boolean anyMatch(Predicate)
:测试是否至少有一个元素满足测试条件。最后一个常用的方法是forEach()
,它可以循环处理Stream
的每个元素,我们经常传入System.out::println
来打印Stream
的元素:
Stream s = ...
s.forEach(str -> {
System.out.println("Hello, " + str);
});
Stream
提供的常用操作有:
转换操作:map()
,filter()
,sorted()
,distinct()
;
合并操作:concat()
,flatMap()
;
并行处理:parallel()
;
聚合操作:reduce()
,collect()
,count()
,max()
,min()
,sum()
,average()
;
其他操作:allMatch()
, anyMatch()
, forEach()
。