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能够在运行时根据具体硬件环境和数据特性进行优化,如减少迭代次数、缓存中间结果等。
典型用法示例:
java
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
例子1:
java
// 使用Stream API统计单词列表中长度大于5的单词数量
long count = words.stream()//数据源
.filter(word -> word.length() > 5)//中间操作,这是一种lambda表达式的写法
.count();//终端操作
例子2:
java
// 使用匿名内部类改写Stream API统计单词列表中长度大于5的单词数量
long count = words.stream()
.filter(
new Predicate<String>() {//这是一种匿名内部类(老写法),等同于上面的lambda写法
@Override
public boolean test(String word) {
return word.length() > 5;
}
})
.count();
例子3:
java
// 将单词列表转换为大写并连接成一个字符串
String result = words.stream()//数据源
.map(String::toUpperCase)//中间操作(它是Java 8及以后版本引入的lambda表达式的一种简洁表示形式,Java中的方法引用(Method Reference)语法,看例子4)
.collect(Collectors.joining(", "));//终端操作
例子4:
java
// 将单词列表转换为大写并连接成一个字符串
String result = words.stream()//数据源
.map(word -> word.toUpperCase())
//中间操作,word是lambda表达式中的参数,word.toUpperCase()则是对参数应用String类的toUpperCase()方法,将其转换为大写。
//方法引用String::toUpperCase直接指定了要使用的类(String)和方法名(toUpperCase)
.collect(Collectors.joining(", "));//终端操作
例子5:
java
// 并行计算单词列表的总长度
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的元素。
示例:
java
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,其中包含应用函数结果的元素。
示例:
java
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。
示例:
java
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。
示例:
java
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个元素。这两个操作可以用于分页或取部分数据。
示例:
java
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中唯一的元素,即去除重复项。
示例:
java
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
没有给代码注释和结果,上边的例子已经很好的了解了,建议心写出对应的结果后建一个main函数复制代码进去跑一跑。
这些中间操作可以灵活组合,形成复杂的流处理管道,使得代码更加简洁、易于阅读和维护。中间操作的惰性执行特性有助于提高性能,尤其是在处理大量数据或进行并行计算时。