Java Stream 流详解
一、Stream 概述
创建
中间操作
终端操作
集合/数组
Stream
结果
Stream(流)是 Java 8 引入的一个新的抽象层,它允许以声明性方式处理数据集合。Stream API 旨在让编程更加关注数据处理的 “做什么” 而不是 “怎么做” 。
特点
非存储 : Stream 不是数据结构,不存储元素函数式 : Stream 操作不会修改源数据惰性求值 : 中间操作不会立即执行,而是等到终端操作时才执行可消费性 : 一个 Stream 只能被消费(遍历)一次并行处理 : 可以轻松地实现并行处理
与集合的区别
流Stream
集合Collection
处理元素
内部迭代
声明式处理
存储元素
外部迭代
命令式处理
特性 集合 Collection 流 Stream 数据存储 存储元素 不存储元素 迭代方式 外部迭代(显式控制) 内部迭代(隐式控制) 处理方式 命令式(How to do) 声明式(What to do) 使用次数 可多次使用 只能使用一次 处理时机 立即处理 延迟处理(惰性求值)
二、Stream 创建方式
1. 从集合创建
List < String > list = Arrays . asList ( "a" , "b" , "c" ) ;
Stream < String > stream = list. stream ( ) ;
Set < String > set = new HashSet < > ( Arrays . asList ( "a" , "b" , "c" ) ) ;
Stream < String > stream = set. stream ( ) ;
Map < String , Integer > map = new HashMap < > ( ) ;
map. put ( "a" , 1 ) ;
map. put ( "b" , 2 ) ;
map. put ( "c" , 3 ) ;
Stream < String > keyStream = map. keySet ( ) . stream ( ) ;
Stream < Integer > valueStream = map. values ( ) . stream ( ) ;
Stream < Map. Entry < String , Integer > > entryStream = map. entrySet ( ) . stream ( ) ;
2. 从数组创建
String [ ] arr = { "a" , "b" , "c" } ;
Stream < String > stream = Arrays . stream ( arr) ;
Stream < String > stream = Arrays . stream ( arr, 0 , 2 ) ;
3. 从值创建
Stream < String > stream = Stream . of ( "a" , "b" , "c" ) ;
4. 使用生成器
Stream < Integer > stream = Stream . iterate ( 0 , n -> n + 2 ) . limit ( 5 ) ;
Stream < Double > stream = Stream . generate ( Math :: random ) . limit ( 5 ) ;
5. 从文件创建
try ( Stream < String > lines = Files . lines ( Paths . get ( "file.txt" ) ) ) { lines. forEach ( System . out:: println ) ;
}
6. 创建空 Stream
Stream < String > emptyStream = Stream . empty ( ) ;
三、Stream 操作类型
Stream 操作可分为两大类:中间操作(Intermediate Operations)和终端操作(Terminal Operations)。
1. 中间操作(返回 Stream)
中间操作用于对 Stream 进行变换和筛选,但不会执行计算,而是记住要进行的操作,返回一个新的 Stream。
filter()
map()
sorted()
Stream<T>
Stream<T>
Stream<R>
Stream<R>
2. 终端操作(返回非 Stream)
终端操作会从 Stream 中生成结果,执行终端操作后,Stream 被消费,不能再使用。
collect()
forEach()
reduce()
Stream<T>
Collection<T>
void
Optional<T>
四、常用 Stream 操作
1. 中间操作
过滤和筛选
方法 描述 示例 filter()
过滤元素 stream.filter(n -> n > 10)
distinct()
去除重复元素 stream.distinct()
limit()
限制元素数量 stream.limit(5)
skip()
跳过前 n 个元素 stream.skip(3)
映射和转换
方法 描述 示例 map()
转换元素 stream.map(String::toUpperCase)
flatMap()
扁平化嵌套流 stream.flatMap(List::stream)
mapToInt()
转换为 IntStream stream.mapToInt(String::length)
mapToLong()
转换为 LongStream stream.mapToLong(x -> (long)x)
mapToDouble()
转换为 DoubleStream stream.mapToDouble(x -> (double)x)
排序
方法 描述 示例 sorted()
自然顺序排序 stream.sorted()
sorted(Comparator)
自定义排序 stream.sorted(Comparator.reverseOrder())
查看元素
方法 描述 示例 peek()
查看元素(调试用) stream.peek(System.out::println)
2. 终端操作
遍历
方法 描述 示例 forEach()
遍历每个元素 stream.forEach(System.out::println)
forEachOrdered()
按顺序遍历每个元素 stream.forEachOrdered(System.out::println)
聚合
方法 描述 示例 count()
计算元素数量 stream.count()
max()
获取最大值 stream.max(Comparator.naturalOrder())
min()
获取最小值 stream.min(Comparator.naturalOrder())
reduce()
归约操作 stream.reduce(0, Integer::sum)
收集
方法 描述 示例 collect()
收集到集合中 stream.collect(Collectors.toList())
toArray()
转换为数组 stream.toArray()
匹配
方法 描述 示例 anyMatch()
任一元素匹配 stream.anyMatch(n -> n > 10)
allMatch()
所有元素匹配 stream.allMatch(n -> n > 0)
noneMatch()
没有元素匹配 stream.noneMatch(n -> n < 0)
查找
方法 描述 示例 findFirst()
获取第一个元素 stream.findFirst()
findAny()
获取任意元素 stream.findAny()
五、Collectors 收集器
Collectors
类提供了许多静态工厂方法,用于创建收集器。常用的收集器如下:
1. 收集到集合
List < String > list = stream. collect ( Collectors . toList ( ) ) ;
Set < String > set = stream. collect ( Collectors . toSet ( ) ) ;
ArrayList < String > arrayList = stream. collect ( Collectors . toCollection ( ArrayList :: new ) ) ;
2. 转换为字符串
String joined = stream. collect ( Collectors . joining ( ", " ) ) ;
3. 统计
IntSummaryStatistics stats = stream. collect ( Collectors . summarizingInt ( String :: length ) ) ;
double average = stats. getAverage ( ) ;
int max = stats. getMax ( ) ;
4. 分组和分区
Map < Integer , List < String > > groupedByLength = stream. collect ( Collectors . groupingBy ( String :: length ) ) ;
Map < Boolean , List < String > > partitioned = stream. collect ( Collectors . partitioningBy ( s -> s. length ( ) > 3 ) ) ;
5. 聚合
int total = stream. collect ( Collectors . summingInt ( String :: length ) ) ;
double avg = stream. collect ( Collectors . averagingInt ( String :: length ) ) ;
Optional < String > longest = stream. collect ( Collectors . maxBy ( Comparator . comparing ( String :: length ) ) ) ;
六、Stream 与集合结合使用的案例
案例 1: 过滤和转换
List < Person > persons = Arrays . asList ( new Person ( "Alice" , 25 ) , new Person ( "Bob" , 30 ) , new Person ( "Charlie" , 35 ) , new Person ( "David" , 40 )
) ;
List < String > names = persons. stream ( ) . filter ( p -> p. getAge ( ) > 30 ) . map ( Person :: getName ) . map ( String :: toUpperCase ) . collect ( Collectors . toList ( ) ) ;
案例 2: 分组和统计
List < Product > products = Arrays . asList ( new Product ( "Apple" , "Fruit" , 1.5 ) , new Product ( "Banana" , "Fruit" , 0.8 ) , new Product ( "Carrot" , "Vegetable" , 0.5 ) , new Product ( "Potato" , "Vegetable" , 0.4 )
) ;
Map < String , Double > avgPriceByCategory = products. stream ( ) . collect ( Collectors . groupingBy ( Product :: getCategory , Collectors . averagingDouble ( Product :: getPrice ) ) ) ;
案例 3: 扁平化处理嵌套集合
List < List < Integer > > nestedList = Arrays . asList ( Arrays . asList ( 1 , 2 , 3 ) , Arrays . asList ( 4 , 5 , 6 ) , Arrays . asList ( 7 , 8 , 9 )
) ;
int sum = nestedList. stream ( ) . flatMap ( List :: stream ) . mapToInt ( Integer :: intValue ) . sum ( ) ;
案例 4: 排序和限制
List < User > users = getUsers ( ) ;
List < User > topThreeOldestUsers = users. stream ( ) . sorted ( Comparator . comparing ( User :: getAge ) . reversed ( ) ) . limit ( 3 ) . collect ( Collectors . toList ( ) ) ;
案例 5: 使用 reduce 进行自定义聚合
List < Order > orders = getOrders ( ) ;
double totalAmount = orders. stream ( ) . map ( Order :: getAmount ) . reduce ( 0.0 , Double :: sum ) ;
案例 6: 并行处理提高性能
long count = numbers. stream ( ) . filter ( n -> n > 1000 ) . count ( ) ;
long count = numbers. parallelStream ( ) . filter ( n -> n > 1000 ) . count ( ) ;
七、primitive 流
Java 8 提供了专门用于处理基本数据类型的流:IntStream
、LongStream
和 DoubleStream
。这些流提供了针对基本类型的特殊操作,避免了装箱和拆箱的开销。
创建基本类型流
int [ ] numbers = { 1 , 2 , 3 , 4 , 5 } ;
IntStream intStream = Arrays . stream ( numbers) ;
IntStream range = IntStream . range ( 1 , 6 ) ;
IntStream rangeClosed = IntStream . rangeClosed ( 1 , 5 ) ;
基本类型流的特殊操作
OptionalDouble avg = IntStream . rangeClosed ( 1 , 10 ) . average ( ) ;
int sum = IntStream . rangeClosed ( 1 , 10 ) . sum ( ) ;
OptionalInt max = IntStream . rangeClosed ( 1 , 10 ) . max ( ) ;
Stream < Integer > boxed = IntStream . rangeClosed ( 1 , 10 ) . boxed ( ) ;
八、并行流
并行流允许并行处理流中的元素,适用于大型数据集和计算密集型任务。
创建并行流
List < String > list = Arrays . asList ( "a" , "b" , "c" ) ;
Stream < String > parallelStream = list. parallelStream ( ) ;
Stream < String > parallel = list. stream ( ) . parallel ( ) ;
并行流的注意事项
状态依赖操作 : 避免使用依赖于元素顺序或之前元素处理结果的操作副作用 : 避免修改共享状态合并成本 : 某些情况下合并结果的成本可能高于并行处理带来的收益数据量 : 对于小数据集,并行处理可能比顺序处理更慢线程安全 : 确保在使用并行流时的操作是线程安全的
List < Integer > numbers = new ArrayList < > ( ) ;
IntStream . range ( 0 , 1000 ) . parallel ( ) . forEach ( numbers:: add ) ;
List < Integer > numbers = IntStream . range ( 0 , 1000 ) . parallel ( ) . boxed ( ) . collect ( Collectors . toList ( ) ) ;
九、Stream 性能考虑
1. 何时使用流
处理集合元素时,尤其是进行过滤、转换、分组等操作 需要链式处理数据时 需要进行函数式和声明式编程时
2. 何时不使用流
对于简单的循环操作,传统的 for 循环可能更高效 需要直接修改元素时 需要使用索引进行复杂操作时 需要多次遍历同一集合时
3. 性能优化技巧
减少中间操作 : 尽量减少中间操作的数量,尤其是会生成新集合的操作适当使用并行流 : 对于大型数据集,考虑使用并行流短路操作 : 使用 limit()
, findFirst()
等短路操作可以在满足条件时立即结束处理优化操作顺序 : 将过滤操作放在前面可以减少后续操作的数据量避免不必要的装箱/拆箱 : 使用专门的基本类型流
stream. map ( String :: length ) . filter ( l -> l > 5 ) . limit ( 10 ) ;
stream. filter ( s -> s. length ( ) > 5 ) . limit ( 10 ) . map ( String :: length ) ;
十、常见问题与最佳实践
1. Stream 只能使用一次
Stream < String > stream = list. stream ( ) ;
stream. forEach ( System . out:: println ) ;
stream. forEach ( System . out:: println ) ;
2. 避免在流操作中修改源数据
list. stream ( ) . forEach ( list:: remove ) ;
List < String > filtered = list. stream ( ) . filter ( predicate) . collect ( Collectors . toList ( ) ) ;
3. 注意避免无限流导致的问题
Stream . iterate ( 0 , n -> n + 1 ) . limit ( 10 ) . forEach ( System . out:: println ) ;
4. 使用合适的收集器
Set < String > uniqueNames = stream. collect ( Collectors . toSet ( ) ) ;
5. 函数保持纯净
stream. map ( s -> s. toUpperCase ( ) ) . collect ( Collectors . toList ( ) ) ;
List < String > result = new ArrayList < > ( ) ;
stream. forEach ( s -> result. add ( s. toUpperCase ( ) ) ) ;
总结
Java Stream API 提供了一种简洁、声明式的方式来处理集合数据,使代码更加清晰、简洁。通过合理使用 Stream 的各种操作,可以大大提高编码效率和代码可读性。但也需注意 Stream 的使用场景和性能考虑,在合适的场景选择合适的工具。