这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
阅读《Java实战》,本文为第5章总结,主要系统全面的介绍Steam的API,包含筛选、切片和映射,查找与匹配、归约操作、数值流、以及流的各种构建方法。
1、筛选
从流中筛选出目标对象,主要分为两个:
- filter(Predicate):基于谓词(返回boolean的函数式接口)筛选过滤
- distinct() : 筛选去重
- limit(n) :截断流,即返回一个不超过指定长度n的流
- skip(n):跳过前面n个元素,和limit互补,实现类似分页功能
使用示例:
// filter示例:过滤出素食的选项
List<Dish> vegetarianMenu =
menu.stream()
.filter((m) -> m.isVegetarian())// lambda实现
//.filter(Dish::isVegetarian) // 方法引用
.collect(Collectors.toList());
vegetarianMenu.forEach(
// (Dish d) -> System.out.println(d) //lambda实现
System.out::println
);
// distinct示例:筛选出偶数并去重复
List<Integer> intArr = Arrays.asList(1,5,4,2,3,3,2,4,6);
//输出:426
intArr.stream()
.filter(i -> i%2==0)
.distinct()
.forEach(System.out::print);
// limit示例:
// 输出:42
intArr.stream()
.filter(integer -> integer%2==0)
.limit(2)
.forEach(System.out::print);
// skip示例:
// 输出:26
intArr.stream()
.filter(integer -> integer%2==0)
.distinct()
.skip(1)
.forEach(System.out::print);
2、映射
将每个元素映射处理成一个新的元素,主要实现有:
- map(Function<T,R> f):将函数应用到每个元素上,并将其转换成一个新的元素。
- mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntSteam。
- flatMap(Function f):接收一个函数作为参数,将流中的每个值都转换成另外一个流,然后把所有流连接成一个流。
//map示例:Dish对象映射成dishName
List<String> dishNames =
Dish.menu.stream()
.map(
//(Dish dish) -> dish.getName() //基于Lambda
Dish::getName // 基于方法引用
).collect(Collectors.toList());
// [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
System.out.println(dishNames);
//map的复合示例:除了映射成dishName的字符长度
List<Integer> dishNameLengths =
Dish.menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(Collectors.toList());
// [4, 4, 7, 12, 4, 12, 5, 6, 6]
System.out.println(dishNameLengths);
//flatMap示例:将单词数组,按字母整体去重处理
String[] helloWorld = {"Hello","World"};
List<String> uniqueCharacters =
Arrays.stream(helloWorld)
.map(word -> word.split(""))//Stream<String[]> 拆分成一个个字符的数组流
.flatMap(Arrays::stream)//将所有数组流汇聚成一个新流
.distinct()
.collect(Collectors.toList());
// [H, e, l, o, W, r, d]
System.out.println(uniqueCharacters);
3、查找与匹配
查找或判断数据流中某些元素是否匹配一个给定的属性,常见API如下:
- anyMatch(Predicate p):至少有一个元素匹配给定的谓词
- allMatch(Predicate p):检查是否匹配所有元素
- noneMatch(Predicate p):检查是否没有匹配的元素
- findAny():返回当前流中的任意元素(注意,返回是Optional对象)
- findFirst():返回第一个匹配的元素(和findAny区别是并发流上)
//anyMatch: 是否存在素食
System.out.println(
Dish.menu.stream()
.anyMatch(Dish::isVegetarian)
);//true
//allMatch:所有菜热量是否<1000
System.out.println(
Dish.menu.stream().allMatch(dish -> dish.getCalories()<1000)
);//true
// findAny : 返回一道素食
Optional<Dish> dish =
Dish.menu.stream()
.filter(Dish::isVegetarian)
.findAny();
//french fries#true
dish.ifPresent(dish1 ->
System.out.println(dish1.getName()+"#"+dish1.isVegetarian()));
4、归约操作
将流中的元素反复结合起来,从而得到一个值,比如最大值、热量总和,这样的操作统称为归约操作。
List<Integer> intList = Arrays.asList(1,3,5,2,4,6);
// 求和计算
Integer sum = intList.stream().reduce(0,(a,b) -> a+b);
Integer sum1 = intList.stream().reduce(0,Integer::sum);
System.out.println(sum+"#"+sum1);//21#21
//无初始值,返回Optional<T>
Optional<Integer> sum2 = intList.stream().reduce(Integer::sum);
sum2.ifPresent(System.out::println);//21
// 求最值示例:
Optional<Integer> maxOpt = intList.stream().reduce((x,y) -> x>y?x:y);
Optional<Integer> maxOpt1 = intList.stream().reduce(Integer::max);
System.out.println(maxOpt.get()+"#"+maxOpt1.get());//6#6
intList.stream().reduce(Integer::min).ifPresent(System.out::println);//1
5、数值流
这块主要基于数值特点,有两个特殊场景,规避数值类型的装箱问题的基础类型流特化、数值范围两个。
5.1 基础类型流特化
使用reduce对Integer类似对象进行数值计算暗含装拆箱成本,因此StreamAPI提供了IntStream、DoubleStream、LongStream三个基础类型流特化接口来处理,避免了潜在的装箱成本。
// 映射成数值流
IntStream intStream = Dish.menu.stream()
.mapToInt(Dish::getCalories);
// 结果返回的是OptionalInt
OptionalInt optionalInt = intStream.max();
//orElse值不存在时默认值
System.out.println(optionalInt.orElse(-999));
System.out.println("----------");
// 装箱转换成Stream
intStream = Dish.menu.stream()
.mapToInt(Dish::getCalories);
Stream<Integer> integerStream = intStream.boxed();
integerStream.forEach(System.out::println);
5.2 数值范围
数值流针对生产指定范围数字的需求,提供了range和rangeClosed两个方法,有两个参数,指定范围,后面方法为闭区间。
// 数值范围方法
IntStream evenNumbers = IntStream.rangeClosed(1, 10)
.filter(n -> n % 2 == 0);
evenNumbers.forEach(i -> System.out.print(i+","));//2,4,6,8,10,
6、构建流
数据元素源时Stream数据处理操作的基石,本小节主要系统介绍除了Collection外所有可能的流生成方式。
- 由值创建流:利用
Stream.of()可以直接将值对象封装成Stream - 数组创建:使用
Arrays.stream()静态方法,将数组直接转换成流 - 文件生成流:主要利用
java.nio.file.Files工具类来实现,比如lines()方法 - 由函数生成流:主要时使用
Stream.iterate(final T seed, final UnaryOperator<T> f)和Stream.generate()方法来循环调用函数生成无限流。
// 基于值序列创建
Stream<String> stream = Stream.of("Java","In","Action");
stream.map(String::toUpperCase).forEach(System.out::println);
// 基于数组创建
int[] intArr = {1,3,5,2,4,6};
int sum = Arrays.stream(intArr).sum();
System.out.println(sum);
// 基于文件创建
Path path = Paths.get(BuildingStreams.class.
getClassLoader().getResource("data.txt").toURI());
try(Stream<String> lineStream = Files.lines(path)){
lineStream.forEach(System.out::println);
}
// 基于函数生成:因为无限流,所以配合limit使用
Stream.iterate(0,n -> n+2)
.limit(5)
.forEach(e -> System.out.print(e+","));//0,2,4,6,8,
Stream.generate(Math::random)
.limit(5)
.forEach(e -> System.out.print(e+","));//随机生成5个随机数
本文涉及的代码示例地址