JAVA Stream流

发布于:2024-04-19 ⋅ 阅读:(25) ⋅ 点赞:(0)

Stream流

是Java 8引入的一种处理数据的强大工具,它提供了一种声明式、高效且易于并行化的编程模型,用于对集合、数组或其他数据源中的元素进行各种计算和操作。Stream API的核心思想是将数据作为一系列元素的序列(流)进行处理,而不是直接操作数据本身。
值得注意的是,流(Stream)是一种基于支持一次性处理数据的数据源的元素序列,流只能使用一次。

以下是对Stream流的详细说明:

核心概念:

数据源(Source): Stream的起点,可以是集合、数组、I/O通道、生成器函数等,任何能够产生数据序列的源头都可以作为Stream的数据源。
中间操作(Intermediate Operations): 一系列惰性求值可链接的无状态操作,如filter(过滤)、map(映射)、sorted(排序)、limit(限制数量)、distinct(去重)等。这些操作不会立即执行,而是构建一个流水线式的操作链。中间操作返回一个新的Stream对象,原Stream保持不变。
终端操作(Terminal Operations): 一个Stream的生命周期以一个终端操作结束,如collect(收集到集合)、forEach(遍历消费)、count(计数)、anyMatch(是否满足条件)、reduce(归约)等。执行终端操作时,会触发中间操作链的执行,从而完成对数据的实际处理。终端操作的结果通常是具体的计算结果或影响(如更新数据库)。
懒加载(Lazy Evaluation): Stream的中间操作仅定义了如何处理数据,直到遇到终端操作时才开始实际遍历数据源并执行计算。这种延迟执行有助于优化性能,避免不必要的计算。
可并行化(Parallelization): Stream支持并行处理,通过调用parallel()方法可以将串行流转换为并行流,利用多核处理器的优势提高大规模数据处理的效率。并行流会自动划分任务,在多个线程上并行执行中间和终端操作。

主要特点:

声明式编程: 使用Stream API编写代码时,关注的是“做什么”而非“怎么做”。通过组合各种操作符(方法)来描述数据处理逻辑,代码更简洁、易于理解。
函数式风格: Stream API鼓励使用lambda表达式和方法引用来定义操作逻辑,符合函数式编程的范式,有利于写出简洁、无副作用的代码。
避免空指针异常: 对于可能为空的集合,Stream API提供了诸如Optional等工具类来优雅地处理空值情况,避免了因空指针引发的运行时异常。
易于优化: Stream的惰性求值和内部优化机制使得JVM和JDK能够在运行时根据具体硬件环境和数据特性进行优化,如减少迭代次数、缓存中间结果等。

典型用法示例:

List<String> words = Arrays.asList("apple", "banana", "cherry", "date");

例子1:

// 使用Stream API统计单词列表中长度大于5的单词数量
long count = words.stream()//数据源
                 .filter(word -> word.length() > 5)//中间操作,这是一种lambda表达式的写法
                 .count();//终端操作

例子2:

// 使用匿名内部类改写Stream API统计单词列表中长度大于5的单词数量
long count = words.stream()
                 .filter(
                 	new Predicate<String>() {//这是一种匿名内部类(老写法),等同于上面的lambda写法
	                     @Override
	                     public boolean test(String word) {
	                         return word.length() > 5;
	                     }
                 	})
                 .count();

例子3:

// 将单词列表转换为大写并连接成一个字符串
String result = words.stream()//数据源
                     .map(String::toUpperCase)//中间操作(它是Java 8及以后版本引入的lambda表达式的一种简洁表示形式,Java中的方法引用(Method Reference)语法,看例子4)
                     .collect(Collectors.joining(", "));//终端操作

例子4:

// 将单词列表转换为大写并连接成一个字符串
String result = words.stream()//数据源
                     .map(word -> word.toUpperCase())
                     //中间操作,word是lambda表达式中的参数,word.toUpperCase()则是对参数应用String类的toUpperCase()方法,将其转换为大写。
                     //方法引用String::toUpperCase直接指定了要使用的类(String)和方法名(toUpperCase)
                     .collect(Collectors.joining(", "));//终端操作

例子5:

// 并行计算单词列表的总长度
int totalLength = words.parallelStream()//数据源(这个是个并行流,下面会讲解,可以理解为将List中的所有String同时进行流处理)
                      .mapToInt(String::length)//中间操作
                      .sum();//终端操作

获取流的常用方式(前三种常用)

1.通过集合获取流:可以使用集合类中的stream()方法或parallelStream()方法来获取流
2.通过数组获取流:可以使用Arrays类中的stream()方法来获取流。
3.通过Stream.of()方法获取流:可以使用Stream类中的of()方法来获取流。
4.通过Stream.iterate()方法获取流:可以使用Stream类中的iterate()方法来获取流
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(5); // 获取顺序流
5.通过Stream.generate()方法获取流:可以使用Stream类中的generate()方法来获取流
Stream<Double> stream = Stream.generate(Math::random).limit(5); // 获取顺序流
6.通过Files.lines()方法获取流:可以使用Files类中的lines()方法来获取流。
Stream<String> stream = Files.lines(Paths.get("file.txt")); // 获取顺序流

常用方法用法

Stream API中的中间操作是指对流进行某种变换或筛选,但并不立即执行操作,而是返回一个新的Stream对象供后续操作链继续构建。中间操作是惰性的,只有当遇到终端操作(如collect、forEach等)时才会触发整个流管道的执行,通常会有多个可链接的无状态操作,譬如在筛选后再映射
以下是常用的Stream中间操作:
1、过滤(Filtering): filter(Predicate<? super T> predicate) —— 根据给定的谓词(Predicate)筛选出符合条件的元素。返回一个新的Stream,其中包含原Stream中所有使谓词返回true的元素。
示例:

   List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
   //筛选所有的偶数
   List<Integer> evenNumbers = numbers.stream()
                                    .filter(n -> n % 2 == 0)
                                    .collect(Collectors.toList());

2、映射(Mapping): map(Function<? super T, ? extends R> mapper) —— 将每个元素应用给定的函数(Function),并返回一个新的Stream,其中包含应用函数结果的元素。
示例:

   List<String> words = Arrays.asList("hello", "world", "java");
   List<Integer> wordLengths = words.stream()
                                   .map(String::length)
                                   .collect(Collectors.toList());
   

3、扁平化(Flattening): flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) —— 类似于map,但允许将每个元素转换为一个Stream,然后将所有生成的Stream扁平化为一个单一的Stream。
示例:

   List<List<String>> nestedWords = Arrays.asList(
       Arrays.asList("hello", "world"),
       Arrays.asList("java", "stream")
   );
   List<String> flattenedWords = nestedWords.stream()
                                           .flatMap(List::stream)
                                           .collect(Collectors.toList());
   

4、排序(Sorting0): sorted() 或 sorted(Comparator<? super T> comparator) —— 对流中的元素进行自然排序(对于实现了Comparable接口的类型)或按照指定的比较器(Comparator)进行排序。返回一个新的已排序的Stream。
示例:

   List<String> words = Arrays.asList("banana", "apple", "cherry");
   List<String> sortedWords = words.stream()
                                   .sorted()
                                   .collect(Collectors.toList());
   

5、切片(Slicing): limit(long maxSize) 和 skip(long n) —— 分别限制返回的流最多包含多少个元素(前n个)和跳过前n个元素。这两个操作可以用于分页或取部分数据。
示例:

   List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
   List<Integer> firstThreeNumbers = numbers.stream()
                                           .limit(3)
                                           .collect(Collectors.toList());

   List<Integer> remainingNumbers = numbers.stream()
                                           .skip(3)
                                           .collect(Collectors.toList());
   

6、去重(Deduplicating): distinct() —— 返回一个新的Stream,其中包含原Stream中唯一的元素,即去除重复项。
示例:

   List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
   List<Integer> uniqueNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());
   

没有给代码注释和结果,上边的例子已经很好的了解了,建议心写出对应的结果后建一个main函数复制代码进去跑一跑。
这些中间操作可以灵活组合,形成复杂的流处理管道,使得代码更加简洁、易于阅读和维护。中间操作的惰性执行特性有助于提高性能,尤其是在处理大量数据或进行并行计算时。