stream是java 8引入的声明式数据处理特性,通过中间操作和终端操作简化集合处理。1. 创建方式包括:从集合调用stream()或parallelstream();2. 使用arrays.stream()转换数组;3. stream.of()直接传元素;4. generate()生成无限流需配合limit();5. iterate()创建有序无限流也需limit()。常用中间操作有filter过滤、map映射、flatmap展平嵌套结构、distinct去重、sorted排序、peek调试、limit截断、skip跳过。终端操作如foreach遍历、toarray转数组、reduce归约、collect收集结果、min/max找极值、count计数、anymatch/allmatch/nonematch判断匹配、findfirst/findany获取元素。适合使用并行流的场景包括大数据量、复杂计算、无依赖数据、cpu密集型任务且需线程安全。与传统循环相比,stream为声明式、内部迭代更简洁并支持并行处理。

Stream是Java 8引入的一个强大的特性,它允许你以声明式的方式处理集合数据,简化代码并提高效率。可以把它看作是数据流,你可以在上面执行各种操作,例如过滤、映射、排序等,最终得到想要的结果。

Stream的操作可以分为两种:中间操作和终端操作。中间操作返回一个新的Stream,允许你链式调用多个操作。终端操作则会消费Stream,产生一个结果或副作用。

如何创建Stream?
创建Stream的方法有很多,最常见的几种方式包括:
立即学习“Java免费学习笔记(深入)”;

-
从集合创建: 这是最常用的方式,直接调用
Collection接口的stream()或parallelStream()方法即可。List
names = Arrays.asList("Alice", "Bob", "Charlie"); Stream stream = names.stream(); // 创建顺序流 Stream parallelStream = names.parallelStream(); // 创建并行流 -
从数组创建: 使用
Arrays.stream()方法可以将数组转换为Stream。int[] numbers = {1, 2, 3, 4, 5}; IntStream stream = Arrays.stream(numbers); -
使用Stream.of()方法: 可以直接将多个元素作为参数传递给
Stream.of()方法来创建Stream。Stream
stream = Stream.of("Apple", "Banana", "Orange"); -
使用Stream.generate()方法: 可以使用
Stream.generate()方法生成一个无限流,需要提供一个Supplier函数来生成元素。注意: 无限流需要配合limit()方法来限制流的大小,否则会无限循环。Stream
randomStream = Stream.generate(Math::random).limit(10); // 生成10个随机数的流 -
使用Stream.iterate()方法: 可以使用
Stream.iterate()方法生成一个有序的无限流,需要提供一个初始值和一个UnaryOperator函数来生成后续元素。同样,需要配合limit()方法来限制流的大小。Stream
evenNumbers = Stream.iterate(0, n -> n + 2).limit(10); // 生成10个偶数的流
Stream的常用中间操作有哪些?
中间操作会返回一个新的Stream,允许你链式调用多个操作,对数据进行转换和过滤。常见的中间操作包括:
-
filter(Predicate predicate): 过滤Stream中的元素,只保留满足
predicate条件的元素。List
names = Arrays.asList("Alice", "Bob", "Charlie", "Anna"); Stream filteredStream = names.stream().filter(name -> name.startsWith("A")); // 过滤出以"A"开头的名字 -
map(Function mapper): 将Stream中的每个元素映射为另一个元素,可以使用
mapper函数进行转换。List
names = Arrays.asList("Alice", "Bob", "Charlie"); Stream nameLengths = names.stream().map(String::length); // 将名字映射为名字的长度 -
flatMap(Function
> mapper): 将Stream中的每个元素映射为一个Stream,然后将所有Stream连接成一个Stream。这个操作通常用于处理嵌套的集合。想象一下,你有一个List- >,你想把它变成一个List
,flatMap就派上用场了。 List
- > nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d", "e")
);
Stream
flattenedStream = nestedList.stream().flatMap(List::stream); // 将嵌套的List展平 -
distinct(): 去除Stream中重复的元素。
List
numbers = Arrays.asList(1, 2, 2, 3, 3, 3); Stream distinctStream = numbers.stream().distinct(); // 去除重复的数字 -
sorted(): 对Stream中的元素进行排序。可以传入
Comparator来自定义排序规则。List
names = Arrays.asList("Charlie", "Alice", "Bob"); Stream sortedStream = names.stream().sorted(); // 默认按字母顺序排序 Stream sortedStreamByLength = names.stream().sorted(Comparator.comparingInt(String::length)); // 按名字长度排序 -
peek(Consumer action): 对Stream中的每个元素执行
action操作,但不会改变Stream的内容。这个操作通常用于调试,可以在Stream处理过程中查看元素的值。List
names = Arrays.asList("Alice", "Bob", "Charlie"); Stream peekedStream = names.stream().peek(System.out::println); // 打印每个名字 -
limit(long maxSize): 截断Stream,只保留前
maxSize个元素。Stream
numbers = Stream.iterate(1, n -> n + 1); Stream limitedStream = numbers.limit(10); // 只保留前10个数字 -
skip(long n): 跳过Stream中的前
n个元素。Stream
numbers = Stream.iterate(1, n -> n + 1); Stream skippedStream = numbers.skip(5); // 跳过前5个数字
Stream的常用终端操作有哪些?
终端操作会消费Stream,产生一个结果或副作用。常见的终端操作包括:
-
forEach(Consumer action): 对Stream中的每个元素执行
action操作。List
names = Arrays.asList("Alice", "Bob", "Charlie"); names.stream().forEach(System.out::println); // 打印每个名字 -
toArray(): 将Stream中的元素转换为数组。
List
names = Arrays.asList("Alice", "Bob", "Charlie"); String[] nameArray = names.stream().toArray(String[]::new); // 转换为String数组 -
reduce(BinaryOperator accumulator): 将Stream中的元素归约为一个值。可以提供一个初始值,也可以不提供。
List
numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().reduce(0, Integer::sum); // 计算所有数字的和,初始值为0 Optional product = numbers.stream().reduce(Integer::sum); // 计算所有数字的积,没有初始值,返回Optional -
collect(Collector collector): 将Stream中的元素收集到集合中。可以使用
Collectors类提供的各种收集器,例如toList()、toSet()、toMap()等。List
names = Arrays.asList("Alice", "Bob", "Charlie"); List nameList = names.stream().collect(Collectors.toList()); // 收集到List Set nameSet = names.stream().collect(Collectors.toSet()); // 收集到Set Map nameMap = names.stream().collect(Collectors.toMap(name -> name, String::length)); // 收集到Map,key为名字,value为名字的长度 -
min(Comparator comparator): 返回Stream中的最小值。需要提供一个
Comparator来比较元素。List
numbers = Arrays.asList(5, 2, 8, 1, 9); Optional min = numbers.stream().min(Integer::compare); // 找到最小的数字 -
max(Comparator comparator): 返回Stream中的最大值。需要提供一个
Comparator来比较元素。List
numbers = Arrays.asList(5, 2, 8, 1, 9); Optional max = numbers.stream().max(Integer::compare); // 找到最大的数字 -
count(): 返回Stream中元素的个数。
List
names = Arrays.asList("Alice", "Bob", "Charlie"); long count = names.stream().count(); // 计算名字的个数 -
anyMatch(Predicate predicate): 判断Stream中是否存在任意一个元素满足
predicate条件。List
names = Arrays.asList("Alice", "Bob", "Charlie"); boolean anyMatch = names.stream().anyMatch(name -> name.startsWith("A")); // 是否存在以"A"开头的名字 -
allMatch(Predicate predicate): 判断Stream中是否所有元素都满足
predicate条件。List
names = Arrays.asList("Alice", "Anna", "Amy"); boolean allMatch = names.stream().allMatch(name -> name.startsWith("A")); // 是否所有名字都以"A"开头 -
noneMatch(Predicate predicate): 判断Stream中是否没有元素满足
predicate条件。List
names = Arrays.asList("Alice", "Bob", "Charlie"); boolean noneMatch = names.stream().noneMatch(name -> name.startsWith("Z")); // 是否没有名字以"Z"开头 -
findFirst(): 返回Stream中的第一个元素。
List
names = Arrays.asList("Alice", "Bob", "Charlie"); Optional first = names.stream().findFirst(); // 找到第一个名字 -
findAny(): 返回Stream中的任意一个元素。在并行流中,
findAny()的效率通常比findFirst()更高。List
names = Arrays.asList("Alice", "Bob", "Charlie"); Optional any = names.parallelStream().findAny(); // 找到任意一个名字
什么时候应该使用并行流?
并行流可以利用多核CPU的优势,提高处理数据的效率。但是,并行流并非总是最佳选择。以下是一些需要考虑的因素:
- 数据量: 当数据量足够大时,并行流才能体现出优势。对于小数据量,并行流的开销可能超过其带来的性能提升。
- 操作的复杂性: 对于简单的操作,并行流的优势可能不明显。对于复杂的操作,并行流可以显著提高效率。
- 数据之间的依赖性: 如果数据之间存在依赖性,并行流可能会导致错误的结果。
- CPU密集型操作: 并行流更适合CPU密集型操作,例如计算、排序等。对于IO密集型操作,并行流的优势可能不明显。
- 线程安全: 使用并行流时,需要确保操作是线程安全的。
一般来说,当数据量较大,操作比较复杂,且数据之间没有依赖性时,可以考虑使用并行流。
Stream和传统循环有什么区别?
Stream和传统循环的主要区别在于:
- 声明式 vs 命令式: Stream采用声明式编程,你只需要描述你想做什么,而不需要关心如何实现。传统循环采用命令式编程,你需要明确地指定每一步的操作。
- 内部迭代 vs 外部迭代: Stream采用内部迭代,由Stream API负责迭代数据。传统循环采用外部迭代,你需要自己控制迭代过程。
- 代码简洁性: Stream可以简化代码,使代码更易读易懂。
- 并行处理: Stream可以方便地进行并行处理,提高效率。
总的来说,Stream提供了一种更简洁、更高效的数据处理方式。在合适的场景下,使用Stream可以显著提高代码的可读性和性能。










