Java8新特性Stream详解

2023-12-13 21:44:59

一、概念

1、Stream的定义

Stream(流)是一种用于处理集合数据的抽象概念。它可以让我们以一种类似于流水线的方式对数据进行操作和处理。Stream API 是 Java 8 引入的一个功能强大的工具,它提供了一种简洁而高效的方式来处理集合数据。通过使用 Stream,我们可以对集合进行过滤、映射、排序、聚合等操作,而无需编写繁琐的循环和条件语句。Stream 的操作可以串行执行,也可以并行执行,从而提高处理大量数据的效率。

2、Stream 的操作分类

1)中间操作

用于对流中的元素进行处理和转换,但并不会触发流的遍历和处理,会返回一个新的Stream流,可以继续进行其他的中间操作或终端操作。

通过组合不同的中间操作,可以构建出一个流水线,以实现复杂的数据处理逻辑。但需要注意的是,中间操作只有在遇到终端操作时才会被触发执行。

中间操作包括有状态和无状态操作两种:

  • 有状态操作:操作需要维护状态来正确执行,每个元素的处理可能依赖于其他元素的状态或上下文。
    例如,sorted和distinct操作,是需要维护一个状态,一个是记录位置,一个是记录是否出现过。
  • 无状态操作:每个元素的处理都是独立的,不依赖于其他元素的状态。
    例如,filter、map和flatMap,只是根据输入元素生成输出元素,而不会受到其他元素的影响。

2)终止操作

用于触发流的遍历和处理,并产生最终的结果或副作用。在流的最后调用,执行终止操作后,流将无法再使用。

终止操作包括短路操作和非短路操作两种:

  • 短路操作:处理元素时,满足某个条件就立即返回结果,无需处理所有元素。
    例如,findFirst、findAny、anyMatch和allMatch
  • 非短路操作:指必须处理所有元素才能得到最终结果;
    例如,forEach、reduce和collect

二、操作

先获取流,然后可以执行各种中间操作,最终终止操作。

1、获取流

获取流的方式,官方文档内容如下:

Streams can be obtained in a number of ways. Some examples include:
From a Collection via the and methods;stream()parallelStream()
From an array via Arrays.stream(Object[]);
From static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
The lines of a file can be obtained from BufferedReader.lines();
Streams of file paths can be obtained from methods in Files;
Streams of random numbers can be obtained from Random.ints();
Numerous other stream-bearing methods in the JDK, including BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().

也就是可以从一个数据源,如集合、数组中获取流

1)从集合(Collection)获取

可以使用集合的stream()方法来获取一个Stream对象,例如:List、Set、Map

List<Integer> list = Arrays.asList(1, 2, 3);
// 通过List获取
Stream<Integer> stream1 = list.stream();

Set<Integer> set = new HashSet<>() {{
	add(1);
	add(2);
	add(3);
}};
// 通过Set获取
Stream<Integer> stream2 = set.stream();

Map<String, String> map = new HashMap<>();
// 通过Map.entrySet获取
Stream<Map.Entry<String, String>> stream3 = map.entrySet().stream();
// 通过Map.keySet获取
Stream<String> stream4 = map.keySet().stream();
// 通过Map.values获取
Stream<String> stream5 = map.values().stream();

2)从数组(Array)获取

可以使用Arrays.stream()方法来获取一个Stream对象,例如:

Integer[] array = {1, 2, 3};
Stream<Integer> stream = Arrays.stream(array);

3)使用Stream的静态方法获取

Stream类提供了一些静态方法来获取Stream对象,例如:

Stream<Integer> stream = Stream.of(1, 2, 3);
IntStream range = IntStream.range(1, 100);
Stream<Integer> stream2 = Stream.iterate(0, n -> n + 2).limit(10);
Stream<Double> stream3 = Stream.generate(Math::random).limit(5);

4)从文件获取

可以使用java.nio.file.Files类的lines()方法来获取一个Stream对象,读取文件中的行,例如:

Path path = Paths.get("G:\\output.txt");
Stream<String> stream = Files.lines(path);

以上是一些常用的获取方式

2、中间操作

操作的中间环节,对数据源的数据进行操作,当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”

中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线
比如map (mapToInt, flatMap 等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。

我们以下面这个List来演示

List<Forlan> list = new ArrayList<Forlan>() {{
	add(new Forlan("张三", 177, 121));
	add(new Forlan("李四", 166, 140));
	add(new Forlan("王五", 189, 160));
	add(new Forlan("张大", 177, 150));
	add(new Forlan("林黄", 160, 110));
}};

1)filter

filter(Predicate predicate):根据指定的条件筛选元素。

list.stream().filter(p -> p.getName().contains("张")).forEach(System.out::println);
Forlan{name='张三', height=177, weight=121}
Forlan{name='张大', height=177, weight=150}

2)map

map(Function<T, R> mapper):将每个元素映射为另一个元素。

list.stream().map(Forlan::getName).forEach(System.out::println);
张三
李四
王五
张大
林黄

3)flatMap

flatMap(Function<T, Stream> mapper):将每个元素映射为一个Stream对象,并将所有Stream对象的元素合并为一个Stream对象。

list.stream().flatMap(p -> Arrays.asList(p.getHeight(), p.getWeight()).stream()).forEach(System.out::println);
177
121
166
140
189
160
177
150
160
110

4)distinct

distinct():去除重复的元素。

list.stream().map(Forlan::getHeight).distinct().forEach(System.out::println);
177
166
189
160

5)sorted

sorted():对元素进行排序。

list.stream().sorted((a, b) -> a.getHeight() - b.getHeight()).forEach(System.out::println);

按照height进行升序,运行结果如下:

Forlan{name='林黄', height=160, weight=110}
Forlan{name='李四', height=166, weight=140}
Forlan{name='张三', height=177, weight=121}
Forlan{name='张大', height=177, weight=150}
Forlan{name='王五', height=189, weight=160}

6)peek

peek(Consumer action):对每个元素执行指定的操作,不会改变Stream中的元素,主要用于调试和观察Stream中的元素

list.stream().peek(p -> System.out.println(p.getHeight())).map(p -> p.getHeight() % 2 == 0).forEach(System.out::println);

观察元素是否是偶数,运行结果如下:

177
false
166
true
189
false
177
false
160
true

7)limit

limit(long maxSize):限制Stream中元素的数量。

list.stream().limit(1).forEach(System.out::println);

限制返回一个元素,运行结果如下:

Forlan{name='张三', height=177, weight=121}

8)skip

skip(long n):跳过Stream中的前n个元素。

list.stream().skip(2).limit(2).forEach(System.out::println);

跳过2个元素,返回2个元素,运行结果如下:

Forlan{name='王五', height=189, weight=160}
Forlan{name='张大', height=177, weight=150}

3、终止操作

执行操作,返回结果
当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终止操作
比如:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。

1)forEach

forEach(Consumer<? super T> action):对流中的每个元素执行指定的操作,没有返回值。

list.stream().forEach(System.out::println);

输出每个元素,运行结果如下:

Forlan{name='张三', height=177, weight=121}
Forlan{name='李四', height=166, weight=140}
Forlan{name='王五', height=189, weight=160}
Forlan{name='张大', height=177, weight=150}
Forlan{name='林黄', height=160, weight=110}

2)toArray

toArray():将流中的元素转换为数组,返回一个包含流中所有元素的数组。

例如,將List转为数组

Forlan[] array = list.stream().toArray(Forlan[]::new);

3)reduce

reduce(T identity, BinaryOperator accumulator):将流中的元素按照指定的操作进行归约,返回一个值。identity是初始值,accumulator是一个二元操作符,通常用于对流中的元素进行聚合操作。

例如,求总身高

Optional<Integer> reduce = list.stream().map(Forlan::getHeight).reduce(Integer::sum);

4)collect

collect(Collector<? super T, A, R> collector):将流中的元素收集到一个集合中,可以是列表、集合或映射等,collector定义了收集的方式。

例如,将List变为Set

Set<Forlan> collect = list.stream().collect(Collectors.toSet());

5)min

min(Comparator<? super T> comparator):返回流中的最小值,comparator定义了元素的比较方式,返回一个Optional类型的值。

Optional<Forlan> min = list.stream().min(Comparator.comparing(Forlan::getHeight));

返回身高最矮的对象

Optional[Forlan{name='林黄', height=160, weight=110}]

6)max

max(Comparator<? super T> comparator):返回流中的最大值,comparator定义了元素的比较方式,返回一个Optional类型的值。

Optional<Forlan> max = list.stream().max(Comparator.comparing(Forlan::getHeight));

返回身高最高的对象

Optional[Forlan{name='王五', height=189, weight=160}]

7)count

count():返回流中的元素个数,返回一个long类型的值。

例如,统计元素个数

long count = list.stream().count();

8)anyMatch

anyMatch(Predicate<? super T> predicate):判断流中是否存在至少一个元素满足给定的条件,返回一个boolean类型的值

boolean flag = list.stream().anyMatch(p -> p.getHeight() > 180);

判断是否有人身高大于180,运行结果如下:

true

9)allMatch

allMatch(Predicate<? super T> predicate):判断流中的所有元素是否都满足给定的条件,返回一个boolean类型的值

boolean flag = list.stream().allMatch(p -> p.getHeight() > 160);

判断是否所有人身高大于160,运行结果如下:

false

10)noneMatch

noneMatch(Predicate<? super T> predicate):判断流中是否没有任何元素满足给定的条件,返回一个boolean类型的值

boolean flag = list.stream().noneMatch(p -> p.getHeight() > 190);

判断是否不存在身高大于190,运行结果如下:

false

11)findFirst

findFirst():返回流中的第一个元素,返回一个Optional类型的值。

Optional<Forlan> first = list.stream().findFirst();

12)findAny

findAny():返回流中的任意一个元素,返回一个Optional类型的值。

Optional<Forlan> any= list.stream().findAny();

三、综合使用

定义一个公共操作的List,如下:

List<Forlan> list = new ArrayList<Forlan>() {{
	add(new Forlan("张三", 177, 121));
	add(new Forlan("李四", 166, 140));
	add(new Forlan("王五", 189, 160));
	add(new Forlan("张大", 177, 150));
	add(new Forlan("林黄", 160, 110));
}};

1、筛选出满足任意指定条件的Forlan对象

List<Predicate> ruleList = new ArrayList<>();
Predicate<Forlan> nameConditon = c -> c.getName().contains("张");
Predicate<Forlan> heightConditon = c -> c.getHeight() > 180;
Predicate<Forlan> weightConditon = c -> c.getWeight() < 120;
ruleList.add(nameConditon);
ruleList.add(heightConditon);
ruleList.add(weightConditon);
Predicate predicate = ruleList.stream().reduce((a, b) -> a.or(b)).get();

运行结果如下:

Forlan{name='张三', height=177, weight=121}
Forlan{name='王五', height=189, weight=160}
Forlan{name='张大', height=177, weight=150}
Forlan{name='林黄', height=160, weight=110}

2、将List转为Map对象

Map<String, Integer> nameHeightMap = list.stream().collect(Collectors.toMap(Forlan::getName, Forlan::getHeight, (k1, k2) -> k1));

Map<String, Forlan> nameMap = list.stream().collect(Collectors.toMap(Forlan::getName, Function.identity()));

3、按照身高分组并统计每组有多少个

Map<Integer, Long> groupedAndCounted = list.stream()
				.collect(Collectors.groupingBy(Forlan::getHeight, Collectors.counting()));

四、总结

总的来说,就是通过stream把数据放到流水线上,然后执行各种中间加工,最终打包出库。

文章来源:https://blog.csdn.net/qq_36433289/article/details/128152509
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。