Java 8 引入了 Stream API,使得处理集合数据更加高效和灵活。本文将通过一系列示例代码,深入分析 Stream API 的各个特性和用法,涵盖从流的创建到中间操作、终端操作的全面应用。
1. 创建流
1.1 连续整数流
IntStream intStream = IntStream.rangeClosed(1, 8);
intStream.forEach(System.out::println);
使用 IntStream.rangeClosed
方法生成一个包含 1 到 8 的整数流,并打印每个整数。rangeClosed
方法包括结束值,因此生成的流包含 1 到 8 的所有整数。这种方式非常适合需要处理连续整数的场景。
1.2 随机数流
new Random().ints(5).forEach(System.out::println);
这里生成了一个包含 5 个随机整数的流,并打印出来。ints
方法可以生成无限流,因此可以通过其他方法限制数量。这在需要随机数据时非常有用。
1.3 数组创建流
String[] array = {"1", "2", "3"};
Stream<String> arrayStream = Arrays.stream(array);
通过 Arrays.stream
方法,可以直接从数组创建流。这种方式非常方便,尤其是在处理数组数据时。
1.4 直接创建流
Stream<Serializable> directStream = Stream.of(1, "a", 3, new ArrayList<>());
directStream.forEach(System.out::println);
Stream.of
方法允许我们直接创建流,可以包含不同类型的元素。这对于处理混合类型的数据很实用。
1.5 流的连接
Stream<Serializable> directStream1 = Stream.of(1, "a", 3, new ArrayList<>());
Stream<Serializable> directStream2 = Stream.of(1, "a", 3, new ArrayList<>());
Stream<Serializable> concatStream = Stream.concat(directStream1, directStream2);
使用 Stream.concat
方法可以将两个流合并为一个流,这对于处理多个数据源非常有用。
1.6 动态构建流
Stream.Builder<Object> streamBuilder = Stream.builder();
streamBuilder.add("a").add("b").add("c");
// build之后就不能再添加元素,否则报异常
Stream<Object> buildStream = streamBuilder.build();
buildStream.forEach(System.out::println);
Stream.Builder
允许我们动态添加元素并构建流,适合在运行时决定流的内容。
1.7 从文件创建流
Path path = Paths.get("C:\\JavaProjects\\tomato-demo\\demo-tomato-micro\\src\\main\\resources\\file.txt");
try (Stream<String> lines = Files.lines(path)) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
通过 Files.lines
方法可以从文件中逐行读取数据并创建流,适合处理文本文件。
1.8 无限流
Stream<String> constStream = Stream.generate(() -> "Albert").limit(50);
constStream.forEach(System.out::println);
Stream.generate
方法生成无限流,通过 limit
方法限制数量,适合需要重复元素的情况。
1.9 随机数流
Stream.generate(Math::random).limit(5).forEach(System.out::println);
这里生成了 5 个随机数的流,展示了如何使用 generate
方法结合 Math::random
。
1.10 步进流
Stream<Integer> iterateStream = Stream.iterate(0, count -> count + 1).limit(500);
iterateStream.forEach(System.out::println);
Stream.iterate
方法用于生成步进流,适合需要根据某种规则生成序列的情况。
1.11 并行流
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("8");
list.add("4");
Stream<String> parallelStream = list.parallelStream();
parallelStream.forEach(System.out::println);
通过 parallelStream
方法可以创建并行流,利用多核处理器提高处理效率。并行流可以显著提高大数据量处理的性能。
2. 中间操作
2.1 过滤和去重
List<User> users = Stream.of(
new User("周润发", 18, "中国"),
new User("亚索", 30, "小日子"),
new User("贾冰", 49, "中国"),
new User("周润发", 18, "中国")
).collect(Collectors.toList());
users.stream().filter(user -> user.getAge() > 18).forEach(System.out::println);
System.out.println("去重");
users.stream().distinct().limit(2).skip(1).forEach(System.out::println);
使用 filter
方法可以根据条件过滤元素,distinct
方法用于去重。注意,自定义类需要重写 equals
和 hashCode
方法,以确保去重的正确性。
2.2 映射
Stream<String> stringStream = users.stream().map(User::getName);
stringStream.forEach(System.out::println);
map
方法用于将流中的每个元素转换为另一种形式,这里将 User
对象映射为其名字,方便后续处理。
2.3 扁平化流
List<User> users1 = Stream.of(
new User("周润发1", 18, "中国"),
new User("亚索1", 30, "小日子"),
new User("贾冰1", 49, "中国"),
new User("周润发1", 18, "中国")
).collect(Collectors.toList());
List<User> users2 = Stream.of(
new User("周润发2", 18, "中国"),
new User("亚索2", 30, "小日子"),
new User("贾冰2", 49, "中国"),
new User("周润发2", 18, "中国")
).collect(Collectors.toList());
List<User> users3 = Stream.of(
new User("周润发3", 18, "中国"),
new User("亚索3", 30, "小日子"),
new User("贾冰3", 49, "中国"),
new User("周润发3", 18, "中国")
).collect(Collectors.toList());
System.out.println("双层List扁平化");
Stream<List<User>> usersStream = Stream.of(users1, users2, users3);
Stream<User> userStream = usersStream.flatMap(List<User>::stream);
userStream.map(User::getName).forEach(System.out::println);
flatMap
方法用于将嵌套流扁平化,适合处理多层数据结构,简化数据处理流程。
2.4 排序
System.out.println("按名字长度排序");
users.stream()
.sorted(Comparator.comparingInt((User user) -> user.getName().length()).reversed())
.forEach(System.out::println);
System.out.println("按年龄排序");
users.stream()
.sorted(Comparator.comparingInt(User::getAge))
.forEach(System.out::println);
System.out.println("按年龄倒序");
users.stream()
.sorted(Comparator.comparingInt(User::getAge).reversed())
.forEach(System.out::println);
使用 sorted
方法可以对流中的元素进行排序,支持自定义比较器。这里展示了按名字长度和年龄的排序方式。
3. 查找与匹配
3.1 短路操作
boolean anyMatchResult = users.stream().anyMatch(user -> user.getAge() == 18);
System.out.println("anyMatch结果是" + anyMatchResult);
boolean noneMatchResult = users.stream().noneMatch(user -> user.getAge() == 18);
System.out.println("noneMatch结果是" + noneMatchResult);
boolean allMatchResult = users.stream().allMatch(user -> user.getAge() > 0);
System.out.println("allMatch结果是" + allMatchResult);
anyMatch
、noneMatch
和 allMatch
方法用于检查流中是否有元素满足某个条件,适合快速判断。
3.2 查找
Optional<User> findFirstOptionalUser = users.stream().findFirst();
System.out.println("findFirst结果是" + findFirstOptionalUser.get());
findFirstOptionalUser.ifPresent(System.out::println);
Optional<User> findAnyOptionalUser = users.stream().findAny();
System.out.println("findAny结果是" + findAnyOptionalUser);
findAnyOptionalUser.ifPresent(System.out::println);
findFirst
和 findAny
方法用于查找流中的元素,返回 Optional
对象以处理可能为空的情况。
4. 聚合操作
4.1 统计
System.out.println("元素数量" + users.stream().count());
count
方法用于统计流中元素的数量。
4.2 最大值和最小值
Optional<User> max = users.stream().max(Comparator.comparingInt(User::getAge));
System.out.println("最大值");
max.ifPresent(System.out::println);
Optional<User> min = users.stream().min(Comparator.comparingInt(User::getAge));
System.out.println("最小值");
min.ifPresent(System.out::println);
max
和 min
方法用于查找流中的最大值和最小值。
4.3 求和和平均值
IntStream userIntStreamSum = users.stream().mapToInt(User::getAge);
System.out.println("求和" + userIntStreamSum.sum());
IntStream userIntStreamAver = users.stream().mapToInt(User::getAge);
System.out.println("求平均值" + userIntStreamAver.average());
IntStream ageStream = users.stream().mapToInt(User::getAge);
int sum = ageStream.reduce(0, (a, b) -> a + b);
System.out.println("reduce求和" + sum);
使用 mapToInt
方法将流中的元素转换为 IntStream
,然后可以进行求和、求平均等操作。reduce
方法用于将流中的元素合并为一个结果,这里用于求和。
4.4 字符串拼接
String joinName = users.stream().map(User::getName).reduce("", (a, b) -> a + b + " ");
System.out.println("字符串拼接" + joinName);
reduce
方法用于将流中的元素合并为一个结果,这里用于字符串拼接。
4.5 收集
System.out.println("收集" + users.stream().map(User::getCountry).collect(Collectors.toList()));
Map<String, Integer> name_age = users.stream().distinct().collect(Collectors.toMap(User::getName, User::getAge));
System.out.println("名字-年龄Map" + name_age);
collect
方法用于将流中的元素收集到集合中,支持多种收集器,如 toList
、toMap
等。这里展示了如何收集国家信息和生成名字-年龄的映射。
5. 分组与分区
5.1 分组
Map<String, List<User>> countryGroup = users.stream().collect(Collectors.groupingBy(User::getCountry));
System.out.println("分组" + countryGroup);
groupingBy
方法用于根据某个属性将流中的元素分组,适合统计和分析。
5.2 分区
Map<Boolean, List<User>> partitioningBy = users.stream().collect(Collectors.partitioningBy(user -> user.getAge() > 18));
System.out.println(partitioningBy);
partitioningBy
方法根据条件将流中的元素分为两个部分,返回一个 Map
。
5.3 字符串连接
String join = users.stream().map(User::getName).collect(Collectors.joining(","));
System.out.println(join);
使用 Collectors.joining
方法可以将流中的字符串连接成一个结果字符串,方便输出。
5.4 统计
IntSummaryStatistics ageSummarizingInt = users.stream().collect(Collectors.summarizingInt(User::getAge));
System.out.println("求和" + ageSummarizingInt.getSum());
System.out.println("最大值" + ageSummarizingInt.getMax());
System.out.println("平均值" + ageSummarizingInt.getAverage());
System.out.println("数量" + ageSummarizingInt.getCount());
System.out.println("最小值" + ageSummarizingInt.getMin());
Collectors.summarizingInt
方法可以快速获取流中元素的统计信息,如求和、最大值、最小值等。
6. 并行流
6.1 并行处理
List<String> parallelList = new ArrayList<>();
parallelList.add("A");
parallelList.add("P");
parallelList.add("P");
parallelList.add("L");
parallelList.add("E");
parallelList.add("S");
// 这样的foreach不能保证元素顺序
System.out.println("不按顺序来");
parallelList.parallelStream().map(String::toLowerCase).forEach(System.out::println);
// foreach order可以保证原始顺序
System.out.println("按顺序来");
parallelList.parallelStream().map(String::toLowerCase).forEachOrdered(System.out::println);
并行流可以提高处理效率,但要注意输出顺序可能不一致。使用 forEachOrdered
可以保证顺序。
6.2 并行流的线程
System.out.println("不按顺序来");
parallelList.parallelStream().map(String::toLowerCase).forEach(item -> {
System.out.println("item: " + item + " Thread ->" + Thread.currentThread().getName());
});
以查看并行流中每个元素处理时所在线程的名称,帮助理解并行处理的工作方式。
6.3 并行流数据收集的顺序
List<String> parallelCollect = parallelList.parallelStream().map(String::toLowerCase).collect(Collectors.toList());
System.out.println("并行流收集数据问题" + parallelCollect);
并行流在数据收集时也会保持顺序,这对于需要顺序输出的场景非常重要。