系统学习Java新特性-Stream API使用

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

阅读《Java实战》,本文为第5章总结,主要系统全面的介绍Steam的API,包含筛选、切片和映射,查找与匹配、归约操作、数值流、以及流的各种构建方法。

1、筛选

从流中筛选出目标对象,主要分为两个:

  • filter(Predicate):基于谓词(返回boolean的函数式接口)筛选过滤
  • distinct() : 筛选去重
  • limit(n) :截断流,即返回一个不超过指定长度n的流
  • skip(n):跳过前面n个元素,和limit互补,实现类似分页功能

使用示例:

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
java复制代码    // 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):接收一个函数作为参数,将流中的每个值都转换成另外一个流,然后把所有流连接成一个流。
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
java复制代码    //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区别是并发流上)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
java复制代码    //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、归约操作

将流中的元素反复结合起来,从而得到一个值,比如最大值、热量总和,这样的操作统称为归约操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码    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提供了IntStreamDoubleStreamLongStream三个基础类型流特化接口来处理,避免了潜在的装箱成本。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码    // 映射成数值流
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 数值范围

数值流针对生产指定范围数字的需求,提供了rangerangeClosed两个方法,有两个参数,指定范围,后面方法为闭区间。

1
2
3
4
java复制代码    // 数值范围方法
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()方法来循环调用函数生成无限流。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码    // 基于值序列创建
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个随机数

本文涉及的代码示例地址

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%