Java 8 引入的 Stream API
是一个非常强大的工具,可以简化对集合(如 List
、Set
)数据的操作。它支持函数式编程风格的链式调用,使得数据的过滤、转换、聚合等操作更加简洁且富有表现力。在这篇文章中,我们将探索 Java 8 中 Stream API
的常见使用方式,包括元素的筛选、转换、排序、聚合等操作。
1. 创建 Stream
Stream 是对集合元素的序列化处理,它并不修改原集合,而是返回一个新的流。你可以通过以下方式创建 Stream
:
从集合中创建:
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");
Stream<String> stream = list.stream();
从数组中创建:
String[] arr = {"apple", "banana", "cherry", "date"};
Stream<String> stream = Arrays.stream(arr);
使用 Stream.of()
创建:
Stream<String> stream = Stream.of("apple", "banana", "cherry", "date");
2. 筛选元素(filter
)
filter
方法允许你通过指定的条件筛选流中的元素。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> filtered = fruits.stream()
.filter(fruit -> fruit.startsWith("b"))
.collect(Collectors.toList());
System.out.println(filtered); // 输出:[banana]
3. 转换元素(map
)
map
方法用于将流中的每个元素应用某种转换,常用于转换类型或对元素进行操作。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> upperCaseFruits = fruits.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseFruits); // 输出:[APPLE, BANANA, CHERRY, DATE]
4. 去重(distinct
)
distinct
用于去除流中的重复元素,返回一个去重后的流。
List<String> fruits = Arrays.asList("apple", "banana", "apple", "cherry", "banana");
List<String> distinctFruits = fruits.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctFruits); // 输出:[apple, banana, cherry]
5. 排序(sorted
)
sorted
方法用于对流中的元素进行排序,可以使用自然顺序或自定义排序。
升序排序:
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedFruits = fruits.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedFruits); // 输出:[apple, banana, cherry, date]
自定义排序:
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedByLength = fruits.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
System.out.println(sortedByLength); // 输出:[apple, date, banana, cherry]
6. 限制结果数量(limit
)
limit
用于限制流中的元素数量,返回最多 N 个元素。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> limitedFruits = fruits.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(limitedFruits); // 输出:[apple, banana, cherry]
7. 跳过元素(skip
)
skip
用于跳过流中前 N 个元素。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
List<String> skippedFruits = fruits.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(skippedFruits); // 输出:[cherry, date, elderberry]
8. 聚合操作(reduce
)
reduce
用于将流中的元素合并成一个单一的结果,通常用于求和、求积等。
求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum); // 输出:15
求最大值:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
max.ifPresent(System.out::println); // 输出:5
9. 连接字符串(Collectors.joining
)
Collectors.joining()
用于将流中的元素连接成一个字符串。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
String result = fruits.stream()
.collect(Collectors.joining(", "));
System.out.println(result); // 输出:apple, banana, cherry, date
10. 分组(groupingBy
)
groupingBy
用于将流中的元素按某种规则分组,返回一个 Map
,键是分组的依据,值是分组后的元素列表。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apricot");
Map<Character, List<String>> groupedByFirstLetter = fruits.stream()
.collect(Collectors.groupingBy(fruit -> fruit.charAt(0)));
System.out.println(groupedByFirstLetter);
// 输出:{a=[apple, apricot], b=[banana], c=[cherry]}
11. 分区(partitioningBy
)
partitioningBy
用于将流中的元素分成两个组,符合条件的元素一组,不符合条件的元素一组。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "apricot");
Map<Boolean, List<String>> partitionedByA = fruits.stream()
.collect(Collectors.partitioningBy(fruit -> fruit.startsWith("a")));
System.out.println(partitionedByA);
// 输出:{true=[apple, apricot], false=[banana, cherry, date]}
12. 收集成集合(Collectors.toList()
/ toSet()
)
你可以将流中的元素收集到一个 List
或 Set
中。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
List<String> fruitList = fruits.stream()
.collect(Collectors.toList());
System.out.println(fruitList); // 输出:[apple, banana, cherry, date]
Set<String> fruitSet = fruits.stream()
.collect(Collectors.toSet());
System.out.println(fruitSet); // 输出:[apple, banana, cherry, date]
13. 查找最小/最大元素(min
/ max
)
min
和 max
用于查找流中的最小或最大元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> min = numbers.stream()
.min(Integer::compareTo);
min.ifPresent(System.out::println); // 输出:1
14. 匹配操作(allMatch
、anyMatch
、noneMatch
)
allMatch
、anyMatch
和 noneMatch
用于测试流中元素是否满足某些条件。
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
boolean allStartWithA = fruits.stream()
.allMatch(fruit -> fruit.startsWith("a"));
System.out.println(allStartWithA); // 输出:false
boolean anyStartWithA = fruits.stream()
.anyMatch(fruit -> fruit.startsWith("a"));
System.out.println(anyStartWithA); // 输出:true
boolean noneStartWithZ = fruits.stream()
.noneMatch(fruit -> fruit.startsWith("z"));
System.out.println(noneStartWithZ); // 输出:true
下面是Java 8 Stream的升级用法,涵盖了流的组合、并行流、定制收集器等内容。
1. 流的组合与链式操作
流的最大特点之一是它支持链式调用,可以将多个操作组合在一起。常见的操作包括:
- map(): 用于转换流中的元素。
- filter(): 用于根据条件过滤流中的元素。
- flatMap(): 用于将每个元素映射成多个元素,并扁平化成一个流。
- reduce(): 将流的元素结合起来,通常用于聚合操作。
- collect(): 收集流中的元素到集合中,通常结合收集器使用。
例如:
List<String> words = Arrays.asList("java", "stream", "example", "filter", "map");
List<String> result = words.stream()
.filter(word -> word.length() > 4) // 过滤掉长度小于或等于4的单词
.map(String::toUpperCase) // 转换为大写
.sorted() // 排序
.collect(Collectors.toList()); // 收集成List
System.out.println(result); // 输出:[EXAMPLE, FILTER, STREAM]
2. 并行流
Stream API提供了并行流的支持,允许流的操作在多个线程中并行执行,通常通过parallel()
方法来实现。
例如,处理一个大的数据集时,使用并行流可以加快处理速度:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用并行流进行求和
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println(sum); // 输出:55
注意事项:
- 并行流并不总是能提高性能,尤其是在数据量较小或者操作比较简单时,可能会因为线程切换带来性能损失。
- 如果流的操作是无状态的(即没有依赖于外部状态),并行流通常更有效。
3. 自定义收集器(Collector)
Collector
接口提供了流元素如何聚合到容器中的方式。Java 8引入了Collectors
工具类,提供了多种常用的收集器方法,例如toList()
、toSet()
、joining()
等。
自定义收集器
你可以实现自定义的Collector
,来进行复杂的收集操作。以下是一个实现自定义收集器的例子,它将流中的所有元素拼接成一个字符串:
public class StringJoinerCollector implements Collector<String, StringBuilder, String> {
@Override
public Supplier<StringBuilder> supplier() {
return StringBuilder::new;
}
@Override
public BiConsumer<StringBuilder, String> accumulator() {
return (sb, str) -> sb.append(str).append(", ");
}
@Override
public BinaryOperator<StringBuilder> combiner() {
return (sb1, sb2) -> sb1.append(sb2.toString());
}
@Override
public Function<StringBuilder, String> finisher() {
return sb -> sb.toString().isEmpty() ? "" : sb.substring(0, sb.length() - 2);
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
String result = words.stream()
.collect(new StringJoinerCollector());
System.out.println(result); // 输出:apple, banana, cherry, date
4. 流的短路操作
短路操作(Short-circuiting Operations)允许你在处理流时提前结束流的计算。这些操作包括:
- anyMatch(): 如果流中的任意元素匹配给定条件,返回
true
。 - allMatch(): 如果流中的所有元素都匹配给定条件,返回
true
。 - noneMatch(): 如果流中的没有元素匹配给定条件,返回
true
。 - findFirst(): 返回流中的第一个元素。
- findAny(): 返回流中的任意一个元素。
例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); // 判断是否有偶数
System.out.println(anyEven); // 输出:true
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // 判断是否所有元素都是偶数
System.out.println(allEven); // 输出:false
5. flatMap()
的使用
flatMap()
用于将流中的元素转换为另一个流,结果是一个扁平化的流,常用于处理嵌套结构的元素。例如,处理列表中的列表,或者将每个元素映射为多个元素。
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flattenedList = listOfLists.stream()
.flatMap(List::stream) // 扁平化嵌套的列表
.collect(Collectors.toList());
System.out.println(flattenedList); // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
6. reduce()
的使用
reduce()
是一个聚合操作,它通过将流中的元素反复结合起来,生成一个值。最常见的应用场景是求和、求积、最大值、最小值等。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
int sum = numbers.stream()
.reduce(0, Integer::sum); // 初始值为0,后面使用Integer::sum进行累加
System.out.println(sum); // 输出:15
// 求最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
System.out.println(max.get()); // 输出:5
7. 流的状态与无状态操作
- 无状态操作:像
map()
、filter()
这样的操作,不依赖于流的其他元素的状态,每个元素的处理都是独立的。 - 有状态操作:像
distinct()
、sorted()
这样的操作需要了解流的所有元素的状态,因此可能会涉及到排序或去重等操作。
总结
Java 8的Stream API提供了强大的功能,不仅仅是数据流的遍历,它通过一系列中间操作和终端操作,使得对集合的操作更加简洁和灵活。并行流、短路操作、流的合并等高级用法可以帮助你在处理大规模数据时获得更好的性能。通过自定义收集器,你甚至可以实现更加复杂的聚合逻辑。