stream相关api对于集合操作非常方便,可以通过链式编程完成数据的处理,有时候很复杂的数据处理通过使用stream的相关api可以非常方便的完成,使用lambda表达式更可以简化为一行代码,下面就介绍一下stream的相关api方法,感受一下如何使用。
构建stream流的方式有很多种,比较常见的两种方式:
一是通过集合的 .stream() 构建流或 .parallelStream() 构建并行流;二是通过Stream类的相关api构建流。
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
Set<Integer> set = new HashSet<>(list);
// 构建普通流
Stream<Integer> stream1 = list.stream();
Stream<Integer> stream2 = set.stream();
// 构建并行流
Stream<Integer> pstream1 = list.parallelStream();
Stream<Integer> pstream2 = set.parallelStream();
这里的并行流要注意,它默认使用的是系统中的 ForkJoinPool.commonPool
线程池,这个线程池默认大小是CPU核数,如果要调整线程池大小,可以有两种方法调整系统变量:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "4");
或
-Djava.util.concurrent.ForkJoinPool.common.parallelism=4
添加一个打印逻辑验证处理线程名:
pstream1.forEach(item -> System.out.println(Thread.currentThread().getName() + " : " + item));
没有添加上面的参数打印输出内容是:
main : 7
main : 6
ForkJoinPool.commonPool-worker-18 : 2
ForkJoinPool.commonPool-worker-4 : 1
ForkJoinPool.commonPool-worker-18 : 10
main : 9
ForkJoinPool.commonPool-worker-4 : 4
ForkJoinPool.commonPool-worker-25 : 3
ForkJoinPool.commonPool-worker-11 : 8
ForkJoinPool.commonPool-worker-29 : 5
添加配置后的输出内容:
main : 7
main : 6
ForkJoinPool.commonPool-worker-1 : 3
ForkJoinPool.commonPool-worker-1 : 5
ForkJoinPool.commonPool-worker-2 : 9
ForkJoinPool.commonPool-worker-3 : 2
ForkJoinPool.commonPool-worker-1 : 4
ForkJoinPool.commonPool-worker-3 : 1
ForkJoinPool.commonPool-worker-0 : 8
ForkJoinPool.commonPool-worker-2 : 10
可见配置参数确实调整了系统线程池的大小,由于主线程也会参与计算,所以对于计算密集型的服务,线程池一般设置为CPU核数 - 1。
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.forEach(System.out::println);
对于特定类型的stream还有专门的api构建,比如IntStream的range()和rangeClosed()方法构建一个范围内的数据,其中range()方法包含开始数据不包含结束数据,rangeClosed()方法包含开始数据同时包含结束数据。
// 包括开始不包括结束
System.out.println("----------------stream1----------------");
IntStream stream1 = IntStream.range(1, 10);
stream1.forEach(System.out::println);
// 包括开始和结束
System.out.println("----------------stream2----------------");
IntStream stream2 = IntStream.rangeClosed(1, 10);
stream2.forEach(System.out::println);
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
IntStream stream = Arrays.stream(arr);
stream.forEach(System.out::println);
上面就是几种常用的构建stream流方式,使用stream流主要是处理数据,处理数据又分为中间方法和终结方法,它们的主要区别就是中间方法处理完后还可以继续使用stream流进行接下来的处理;而终结方法执行后的流不能继续使用,要想继续使用就必须重新开启一个流再次执行。
下面所有的流使用的都是这个stream做演示:
List<Integer> list = new ArrayList<>(Arrays.asList(3, 6, 10, 5, 5, 2, 6, 4, 9, 7, 8, 1, 8, 9));
Stream<Integer> stream = list.stream();
中间方法执行后获取到的仍然是一个可以使用的流,常用的中间方法如下:
// 筛选大于3且小于8的数据
stream.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i > 3 && i < 8;
}
}).forEach(item -> System.out.println(item));
// lambda表达式
stream.filter(i -> i > 3 && i < 8).forEach(System.out::println);
// 跳过前2条数据返回5条数据
stream.skip(2).limit(5).forEach(System.out::println);
stream.distinct().forEach(System.out::println);
// 默认排序是升序排列
stream.sorted().forEach(System.out::println);
// 自定义排序:降序排列
stream.sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 > o2 ? -1 : (o1.intValue() == o2.intValue() ? 0 : 1);
}
}).forEach(item -> System.out.println(item));
// lambda表达式
stream.sorted((o1, o2) -> o1 > o2 ? -1 : (o1.intValue() == o2.intValue() ? 0 : 1)).forEach(System.out::println);
stream.map(new Function<Integer, String>() {
@Override
public String apply(Integer i) {
return "i-" + i;
}
}).forEach(item -> System.out.println(item));
// lambda表达式
stream.map(i -> "i-" + i).forEach(System.out::println);
stream.flatMap((Function<Integer, Stream<?>>) num -> {
// 因式分解该整数
if(num <= 2) {
return Stream.of(num);
}
List<Integer> nums = new ArrayList<>();
int i = num;
while (i >= 2) {
for(int j = 2; j <= i; j++) {
if(i % j == 0) {
nums.add(j);
i /= j;
break;
}
}
}
return nums.stream();
}).forEach(System.out::println);
注意:map()和flatMap()这两个方法都能实现改变数据类型,主要区别是map()方法修改类型后返回的是单个元素、flatMap()方法修改类型后返回的是一个stream流,主要用于修改类型后返回的是一个集合的场景。
7. 获取数据peek()方法
在数据流处理过程中,我们想查看一下当前数据的内容或记录数据信息到日志中,可以通过peek()方法执行,该方法只是对数据查看而不会改变数据的内容:
// 打印数据
stream.peek(System.out::println).forEach(System.out::println);
peek()方法与forEach()方法的区别是:peek()输出数据后不影响流的继续操作;而forEach()方法输出数据后流不能继续执行。peek()方法执行虽然不能修改元素,但是可以修改元素内属性的值而对流的执行不产生影响。
@Data
@Builder
public class DemoVo {
private int id;
private String name;
}
List<DemoVo> list = Arrays.asList(DemoVo.builder().build(), DemoVo.builder().build(), DemoVo.builder().build());
// 通过peek()方法设置对象的属性值
list.stream().peek(System.out::println).peek(c -> c.setName("name")).forEach(System.out::println);
终结方法执行完后,这个流不能继续执行,常见的终结方法有下面这些:
// 最大值
Integer max = stream.max(Comparator.comparingInt(o -> o)).get();
System.out.println(max);
// 最小值
Integer min = stream.min(Comparator.comparingInt(o -> o)).get();
System.out.println(min);
// 计数
long count = stream.count();
System.out.println(count);
stream.forEach(System.out::println);
Integer[] arr = stream.toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});
// lambda表达式
Integer[] arr = stream.toArray(value -> new Integer[value]);
System.out.println(Arrays.toString(arr));
// 收集到list
List<Integer> list = stream.collect(Collectors.toList());
// 收集到set
Set<Integer> set = stream.collect(Collectors.toSet());
// 收集到map:两个函数分别用于键的生成规则和值的生成规则,要注意键不能重复,否则会抛出异常
Map<Integer, String> map = stream.collect(Collectors.toMap(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer i) {
return i;
}
}, new Function<Integer, String>() {
@Override
public String apply(Integer i) {
return "val-" + i;
}
}));
// lambda表达式
Map<Integer, String> map = stream.collect(Collectors.toMap(i -> i, i -> "val-" + i));