数据源----> 转换 ----> 执行操作得到想要的效果,每次转换原有的流不发生变化,而产生新的流
-
流的操作分为两类:
-
Intermediate
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
-
Termial
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
-
-
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?
其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
-
常见操作:
-
Intermediate:
map(mapToInt,flatMap)
、filter
、distinct
、sorted
、peek
、limit
、skip
、parallel
、sequential
、unorderd
-
Terminal:
foreach
、forEachOrdered
、toArray
、reduce
、collect
、min
、max
、count
、anyMatch
、allMatch
、noneMatch
、findFirst
、findAny
、iterator
-
操作详情:
-
map
作用:把stream流中输入的每一个元素,映射成output到下一个转换中的另外一个元素
例:转换大写
List<String> output = wordList.stream(). map(String::toUpperCase). collect(Collectors.toList());
例:得到对象中的某个元素
List<Long> bookIdList = bookList.stream().map(Book::getBookId).collect(Collectors.toList());
map是一对一的映射
-
flatMap
作用:用于扁平化数据,简单来说就是取消列表或取消嵌套,把列表中的元素释放成单个元素或者将嵌套链表中的元素放到同一个链表当中
例:
List<List<Integer>> nestedList = Arrays.asList( Arrays.asList(1, 2, 3), Arrays.asList(4, 5), Arrays.asList(6, 7, 8) ); List<Integer> flatList = nestedList.stream() .flatMap(List::stream) // 将嵌套列表展平 .collect(Collectors.toList());
-
filter
作用:顾名思义就是提供过滤的功能
例:挑出文章中的所有单词
List<String> output = reader.lines(). flatMap(line -> Stream.of(line.split(REGEXP))). filter(word -> word.length() > 0). collect(Collectors.toList());
首先通过flatMap把每行的元素拼接起来形成一个流,然后保留长度大于0的单词
-
forEach
forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
注意:forEach是一个Terminal操作,完成后会消费掉stream
-
peek
作用:对每个元素进行操作并返回一个新的stream,可以等同于foreach
-
findFirst
作用:是终端操作,用于查找流中的第一个元素(满足条件的元素)并返回一个
Optional
对象。这是一个非常有用的操作,特别是在你需要找到第一个匹配的元素时,但不关心其它元素的顺序或个数时。
Optional
是 Java 8 引入的一个类,用于处理可能包含空值的情况,以减少空指针异常的风险。Optional
可以包装一个非空的值(存在值),也可以表示没有值(空值)。它是一个容器,可以用于更加安全和明确地表示可选值的存在或缺失。以下是
Optional
的主要特点和用途:- 避免空指针异常:
Optional
提供了一种方式来明确处理可能为空的值,从而减少空指针异常的风险。 - 明确可选性: 使用
Optional
使得代码更加清晰,因为它明确表示一个值是可选的,并且需要进行处理。 - 链式调用: 你可以使用链式调用方法来处理
Optional
,例如map
、filter
、orElse
等,使代码更加流畅。 - 避免 null 检查: 使用
Optional
可以避免繁琐的 null 检查,因为Optional
会自动处理值的存在或缺失。 - 不鼓励返回 null: 在方法的返回值上使用
Optional
可以鼓励开发者不返回 null,而是返回一个包装在Optional
中的值或空值。
// 创建一个包含非空值的 Optional Optional<String> nonEmptyOptional = Optional.of("Hello, World"); // 创建一个空的 Optional Optional<String> emptyOptional = Optional.empty(); // 判断 Optional 是否包含值 if (nonEmptyOptional.isPresent()) { String value = nonEmptyOptional.get(); System.out.println("Value: " + value); } // 使用 ifPresent 方法来执行操作,如果 Optional 包含值 nonEmptyOptional.ifPresent(val -> System.out.println("Value: " + val)); // 获取值,如果存在值,否则返回默认值 String result = emptyOptional.orElse("Default Value"); System.out.println("Result: " + result);
return kidsNoticeRepository.findById(noticeId).orElseThrow(() -> new CustomException(ErrorCode.E92107)); //findById()返回的就是一个Optional对象——crudRepository接口中
Stream 中的 findAny、max/min、reduce 等方法都返回 Optional 值
-
reduce
作用:是一种终端操作,用于将流中的元素进行归约操作,生成一个最终的结果。规约操作是将一系列元素合并成一个单一的结果的操作,通常涉及到一些聚合操作,返回值是一个Optional对象,因为可能有空值的情况
例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, (a, b) -> a + b);
用法:
使用一个初始值,比如求和操作中的0,和一个BinaryOperator(一个函数,接收两个参数返回一个结果值),它将初始值和流中当前值作为这个BinaryOperator的参数传给这个二元操作函数返回值作为下一个元素的起始值
// 字符串连接,concat = "ABCD" String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); // 求最小值,minValue = -3.0 double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); // 求和,sumValue = 10, 有起始值 int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); // 求和,sumValue = 10, 无起始值 sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); // 过滤,字符串连接,concat = "ace" concat = Stream.of("a", "B", "c", "D", "e", "F"). filter(x -> x.compareTo("Z") > 0). reduce("", String::concat);
-
limit/skip
作用:限制返回条数和跳过某些条数
有一种情况,如果把limit操作和skip操作加在sorted之后是无法起作用的,因为这些都是Intermediate操作,由于流对所有的数据是一起处理的,所以并不知道排序后的顺序,所以看起来没有起作用
-
sorted
作用:对元素进行排序,尽量把排序放在最后执行,否则一些limit、skip之类的操作可能会不起作用,即:不要在排序后取值
List<Person> persons = Arrays.asList( new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 20) ); List<Person> sortedPersons = persons.stream() .sorted(Comparator.comparing(Person::getAge)) // 按年龄升序排序 .collect(Collectors.toList()); System.out.println("Sorted persons by age: " + sortedPersons);
-
min/max/distinct
可以先排序然后再FindFirst,但是sorted的时间复杂度是O(nlogn),而min、max的时间复杂度是O(n)
-
-
-
总结:
- 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
- 所有 Stream 的操作必须以 lambda 表达式为参数
- 不支持索引访问
- 惰性化
- 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
- 可以是无限的