Stream流的介绍
在 Java 中,Stream 流是 Java 8 引入的一个新的抽象层,用于对集合(如 List、Set 等)中的元素进行一系列的操作,提供了一种高效且易于表达的方式来处理数据序列。下面从多个方面详细介绍 Stream 流。
特点
- 声明式编程:传统的集合操作通常是命令式的,需要手动编写循环来遍历集合并处理元素;而 Stream 流采用声明式编程风格,只需描述想要执行的操作,而无需关心具体的实现细节。
- 惰性求值:Stream 操作分为中间操作和终端操作。中间操作只是定义了一个操作流程,并不会立即执行,只有在遇到终端操作时才会触发整个流的处理过程。
- 可并行处理:Stream 流可以很方便地进行并行计算,利用多核处理器的优势提高处理效率。
创建 Stream 流的方式
从集合创建
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreationExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 从集合创建 Stream
Stream<Integer> stream = numbers.stream();
}
}
从数组创建
import java.util.stream.Stream;
public class StreamCreationFromArray {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
// 从数组创建 Stream
Stream<Integer> stream = Arrays.stream(numbers).boxed();
}
}
使用 Stream.of()方法创建
import java.util.stream.Stream;
public class StreamCreationUsingOf {
public static void main(String[] args) {
// 使用 Stream.of() 方法创建 Stream
Stream<String> stream = Stream.of("apple", "banana", "cherry");
}
}
惰性求值
public class StreamDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 按照字符串长度排序
List<String> collect = names.stream()
.peek(x -> System.out.println("过滤前数据:" + x))
.filter(name -> name.length() > 3)
.peek(x -> System.out.println("过滤后数据:" + x))
.collect(Collectors.toList());
System.out.println(collect);
}
}
Stream是惰性求值,中间操作只是定义了一个操作流程,并不会立即执行,只有在遇到终端操作时才会触发整个流的处理过程,所以上面的代码不是全部数据先执行过滤前逻辑,再执行过滤后逻辑,而是一个元素接着一个元素,先执行过滤前的逻辑,接着执行过滤,再执行过滤后的逻辑。
输出的数据如下:
第一个元素
过滤前数据:Alice
过滤后数据:Alice
第二个元素
过滤前数据:Bob
第三个元素
过滤前数据:Charlie
过滤后数据:Charlie
第四个元素
过滤前数据:David
过滤后数据:David
第五个元素
过滤前数据:Eve
[Alice, Charlie, David]
表格
流类型 | 方法名 | 作用 | 方法类型 |
---|---|---|---|
遍历 | forEach | 遍历数据 | 终端 |
forEachOrdered | 按照流的顺序遍历数据,一般在并行流中使用 | 终端 | |
peek | 在流中执行一些操作,不改变流的数据 | 中间 | |
匹配 | findFirst | 返回流中的第一个元素 | 终端 |
findAny | 返回流中的任意一个元素。顺序流通常放回第一个元素,并行流放 回任意元素,对此运行结果相同,和线程调度和 JVM 优化有关 | 终端 | |
anyMatch | 判断流中是否存在至少一个元素满足给定的条件 | 终端 | |
allMatch | 判断流中的所有元素是否都满足给定的条件 | 终端 | |
noneMatch | 判断流中是否不存在任何元素满足给定的条件 | 终端 | |
映射 | map | 将流中的元素转换成任意类型的对象 | 中间 |
mapToInt | 返回对应的基本类型流 | 中间 | |
mapToLong | 返回对应的基本类型流 | 中间 | |
mapToDouble | 返回对应的基本类型流 | 中间 | |
flatMap | 将流中的每个元素通过给定的函数转换为一个流,然后将所有这 些流合并成一个单一的流,于将嵌套的集合或数组展平成一个单一的流 | 中间 | |
flatMapToInt | 但将流中的每个元素转换为一个基本类型的流 | 中间 | |
flatMapToLong | 但将流中的每个元素转换为一个基本类型的流 | 中间 | |
flatMapToDouble | 但将流中的每个元素转换为一个基本类型的流 | 中间 | |
mapMulti | 是一个通用的转换方法,允许将流中的每个元素映射为 零个、 一个或多个新的元素 | 中间 | |
mapMultiToInt | 专门用于将流中的元素映射为零个、一个或多个int 类型的值 | 中间 | |
mapMultiToLong | 专门用于将流中的元素映射为零个、一个或多个long 类型的值 | 中间 | |
mapMultiToDouble | 专门用于将流中的元素映射为零个、一个或多个 double 类型的值 | 中间 | |
过滤 | filter | 方法用于筛选流中的元素 | 中间 |
去重 | distinct | 用于对数据进行去重 | 中间 |
排序 | sorted | 用于对数据进行排序 | 中间 |
限制 | limit | 方法用于限制流中元素的数量,返回前n个元素 | 中间 |
跳过 | skip | 方法用于跳过流中的前 n 个元素,返回剩余的元素 | 中间 |
归约 | reduce | 方法用于对流中的元素进行聚合操作,将流中的元素组合成一 个单一的结果,计算流中元素的总和、乘积、最大值、最小值等 | 中间 |
聚合 | min | 方法用于查找流中的最小值 | 中间 |
max | 方法用于查找流中的最大值 | 中间 | |
count | 方法用于统计流中元素的数量 | 中间 | |
合并 | concat | 方法用于将两个流合并为一个流 | 中间 |
收集 | collect | 就是把一个流收集起来,最终可以是收集成一个值也可以 收集成一个新的集合 | 终端 |
toList | 将流中的元素收集到一个 List 中 | 终端 | |
toSet | 将流中的元素收集到一个 Set 中 | 终端 | |
toMap | 将流中的元素收集到一个 Map中 | 终端 | |
summingInt | 计算整数属性的总和 | 终端 | |
averagingInt | 计算整数属性的总和 | 终端 | |
maxBy | 查找最大值 | 终端 | |
minBy | 查找最小值 | 终端 | |
groupingBy | 根据某个属性对元素进行分组 | 终端 | |
partitioningBy | 根据某个布尔条件将元素分为两组 | 终端 | |
joining | 将流中的字符串元素连接成一个单一的字符 | 终端 | |
遍历
forEach
介绍
forEach
方法用于对流中的每个元素执行指定的操作。这是一个终端操作,意味着在调用 forEach
后,流的处理将结束,不会再进行后续的操作。
特点
- 无序遍历:对于并行流,
forEach
不保证元素的处理顺序,可能会打乱原始顺序。 - 适用于不需要保持元素顺序的场景。
示例代码
import java.util.Arrays;
import java.util.List;
public class ForEachExample {
public static void main(String[] args) {
List<String> word = Arrays.asList("A", "B", "C", "D");
// 使用 forEach 遍历并打印
word.stream().forEach(word -> System.out.println(word)); // 顺序流顺序固定,ABCD
word.parallelStream().forEach(word -> System.out.println(word)); // 并行流循序不固定,CDBA
}
}
forEachOrdered
介绍
forEachOrdered
方法也用于对流中的每个元素执行指定的操作,但与 forEach
不同的是,forEachOrdered
保证按照流的顺序依次处理每个元素。这在并行流中尤其有用,可以确保元素按原始顺序被处理。
特点
- 有序遍历:即使在并行流中,也会按照流的顺序依次处理元素。
- 适用于需要保持元素顺序的场景。
示例代码
import java.util.Arrays;
import java.util.List;
public class ForEachOrderedExample {
public static void main(String[] args) {
List<String> word = Arrays.asList("A", "B", "C", "D");
// 使用 forEachOrdered 遍历并打印,保证顺序
word.parallelStream().forEachOrdered(word -> System.out.println(word));
}
}
peek
介绍
peek
方法用于在流的每个元素上执行某些操作,但 不会改变流中的数据。它主要用于调试或记录日志等场景,因为它允许你在流的中间操作中插入副作用操作(如打印),而不影响流的处理。
特点
- 中间操作:
peek
是一个中间操作,必须与其他终端操作(如collect
、forEach
等)结合使用。r如果只使用peek不使用终端操作,在里面做的打印等操作不会生效。 - 无副作用:理想情况下,
peek
中的操作不应改变流的状态或外部状态,但在实际使用中,可以用于记录日志等副作用操作。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class PeekExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("A", "BBB", "CC", "DDDD");
// 使用 peek 打印每个word,并收集到新的列表中
List<String> wordList = words.stream()
.peek(word -> System.out.println("处理字母: " + word))
.filter(word -> word.length() > 2)
.peek(word -> System.out.println("过滤后字母: " + word))
.collect(Collectors.toList());
System.out.println("结果列表: " + wordList);
}
}
打印结果如下:
处理字母: A
处理字母: BBB
过滤后字母: BBB
处理字母: CC
处理字母: DDDD
过滤后字母: DDDD
结果列表: [BBB, DDDD]
为什么是这个顺序
Stream 的执行模型
Java Stream 是惰性求值(lazy evaluation)的,这意味着流中的操作不会立即执行,而是等到终端操作(如 collect
、forEach
等)被调用时才会触发整个流水线的执行。在流水线中,中间操作(如 peek
、filter
、map
等)只是定义了流水线的步骤,而不会立即处理数据。触发整个流水线的执行,每一个元素以此走完流水在接着下一个。
虽然 peek
是分开定义的,但它们实际上是在同一个流水线中依次执行的。当终端操作 collect
被调用时,整个流水线会按顺序处理每个元素。因此,对于每个元素,以下步骤会依次执行:
处理字母: A
第一个 peek
打印 “处理字母: A”。
filter
判断 “A” 的长度不大于 2,过滤掉。
处理字母: BBB
第一个 peek
打印 “处理字母: BBB”。
filter
判断 “BBB” 的长度大于 2,保留。
第二个 peek
打印 “过滤后字母: BBB”。
处理字母: CC
第一个 peek
打印 “处理字母: CC”。
filter
判断 “CC” 的长度不大于 2,过滤掉。
处理字母: DDDD
第一个 peek
打印 “处理字母: DDDD”。
filter
判断 “DDDD” 的长度大于 2,保留。
第二个 peek
打印 “过滤后字母: DDDD”。
因此,输出顺序如下:
处理字母: A
处理字母: BBB
过滤后字母: BBB
处理字母: CC
处理字母: DDDD
过滤后字母: DDDD
结果列表: [BBB, DDDD]
匹配
findFirst
介绍
findFirst
方法用于返回流中的第一个元素。这是一个终端操作,调用后流将不再可用。
代码示例
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FindFirstExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
Optional<Integer> firstElement = numbers.stream().findFirst();
firstElement.ifPresent(System.out::println); // 输出: 10
}
}
findAny
介绍
findAny
方法用于返回流中的任意一个元素。对于顺序流,通常返回第一个元素;对于并行流,可能返回任意一个元素(但是同一个集合多次调用放回的是同一个元素)。这是一个终端操作,调用后流将不再可用。
代码示例
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class FindAnyExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
Optional<Integer> anyElement = numbers.stream().findAny();
anyElement.ifPresent(System.out::println); // 输出: 10(顺序流)
// 并行流示例
Optional<Integer> anyElementParallel = numbers.parallelStream().findAny();
anyElementParallel.ifPresent(System.out::println); // 输出: 可能是任意一个元素
}
}
anyMatch
介绍
anyMatch
方法用于判断流中是否存在至少一个元素满足给定的条件。这是一个终端操作,返回一个布尔值。
示例代码
import java.util.Arrays;
import java.util.List;
public class AnyMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
// 判断是否存在大于 45 的元素
boolean exists = numbers.stream().anyMatch(n -> n > 45);
System.out.println(exists); // 输出: true
}
}
allMatch
介绍
allMatch
方法用于判断流中的所有元素是否都满足给定的条件。这是一个终端操作,返回一个布尔值。
示例代码
import java.util.Arrays;
import java.util.List;
public class AllMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
// 判断是否所有元素都大于 5
boolean allGreaterThanFive = numbers.stream().allMatch(n -> n > 5);
System.out.println(allGreaterThanFive); // 输出: true
// 判断是否所有元素都大于 45
boolean allGreaterThanFortyFive = numbers.stream().allMatch(n -> n > 45);
System.out.println(allGreaterThanFortyFive); // 输出: false
}
}
noneMatch
介绍
noneMatch
方法用于判断流中是否不存在任何元素满足给定的条件。这是一个终端操作,返回一个布尔值。
示例代码
import java.util.Arrays;
import java.util.List;
public class NoneMatchExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);
// 判断是否不存在大于 60 的元素
boolean noneGreaterThanSixty = numbers.stream().noneMatch(n -> n > 60);
System.out.println(noneGreaterThanSixty); // 输出: true
// 判断是否不存在大于 45 的元素
boolean noneGreaterThanFortyFive = numbers.stream().noneMatch(n -> n > 45);
System.out.println(noneGreaterThanFortyFive); // 输出: false
}
}
映射
map
介绍
map
方法用于将流中的每个元素转换为另一种类型的对象。它接受一个 Function
作为参数,这个函数会被应用到流中的每个元素上,生成一个新的流。
特点
- 中间操作:
map
返回一个新的流,允许后续的链式操作。 - 灵活性高:可以将元素转换为任意类型的对象。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 将每个单词转换为大写
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(upperCaseWords); // 输出: [APPLE, BANANA, CHERRY]
// 将每个单词转换为它的长度
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(wordLengths); // 输出: [5, 6, 6]
}
}
mapToInt
介绍
mapToInt
方法用于将流中的元素转换为 int
类型的基本类型流 (IntStream
)。它接受一个 ToIntFunction
作为参数,这个函数会被应用到流中的每个元素上,生成一个 IntStream
。
特点
- 中间操作:
mapToInt
返回一个IntStream
,允许后续针对基本类型int
的操作。 - 性能优化:相比于通用的
map
,mapToInt
可以减少装箱(boxing)操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class MapToIntExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 将每个单词转换为它的长度,并计算总长度
int totalLength = words.stream()
.mapToInt(String::length)
.sum();
System.out.println(totalLength); // 输出: 17
// 过滤长度大于5的单词,并打印它们的长度
words.stream()
.mapToInt(String::length)
.filter(length -> length > 5)
.forEach(System.out::println); // 输出: 6, 6
}
}
mapToLong
介绍
mapToLong
方法用于将流中的元素转换为 long
类型的基本类型流 (LongStream
)。它接受一个 ToLongFunction
作为参数,这个函数会被应用到流中的每个元素上,生成一个 LongStream
。
特点
- 中间操作:
mapToLong
返回一个LongStream
,允许后续针对基本类型long
的操作。 - 性能优化:类似于
mapToInt
,mapToLong
可以减少装箱操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;
public class MapToLongExample {
public static void main(String[] args) {
List<Long> numbers = Arrays.asList(100L, 200L, 300L, 400L);
// 将每个数字乘以2
LongStream doubledNumbers = numbers.stream()
.mapToLong(n -> n * 2);
doubledNumbers.forEach(System.out::println); // 输出: 200, 400, 600, 800
// 计算总和
long sum = numbers.stream()
.mapToLong(Long::longValue)
.sum();
System.out.println(sum); // 输出: 1000
}
}
mapToDouble
介绍
mapToDouble
方法用于将流中的元素转换为 double
类型的基本类型流 (DoubleStream
)。它接受一个 ToDoubleFunction
作为参数,这个函数会被应用到流中的每个元素上,生成一个 DoubleStream
。
特点
- 中间操作:
mapToDouble
返回一个DoubleStream
,允许后续针对基本类型double
的操作。 - 性能优化:类似于
mapToInt
和mapToLong
,mapToDouble
可以减少装箱操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;
public class MapToDoubleExample {
public static void main(String[] args) {
List<Double> numbers = Arrays.asList(1.5, 2.5, 3.5, 4.5);
// 将每个数字乘以2
DoubleStream doubledNumbers = numbers.stream()
.mapToDouble(n -> n * 2);
doubledNumbers.forEach(System.out::println); // 输出: 3.0, 5.0, 7.0, 9.0
// 计算平均值
double average = numbers.stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
System.out.println(average); // 输出: 3.0
}
}
flatMap
介绍
flatMap
方法将流中的每个元素通过给定的函数转换为一个流,然后将所有这些流合并成一个单一的流。它主要用于将嵌套的集合或数组展平成一个单一的流。
特点
- 中间操作:
flatMap
返回一个新的流,允许后续的链式操作。 - 灵活性高:可以将元素转换为任意类型的流。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapExample {
public static void main(String[] args) {
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
// 使用 flatMap 将嵌套的列表展平成一个单一的流
List<Integer> flatList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
flatMapToInt
介绍
flatMapToInt
方法将流中的每个元素转换为一个 int
类型的基本类型流 (IntStream
),然后将所有这些流合并成一个单一的 IntStream
。
特点
- 中间操作:
flatMapToInt
返回一个IntStream
,允许后续针对基本类型int
的操作。 - 性能优化:相比于通用的
flatMap
,flatMapToInt
可以减少装箱(boxing)操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class FlatMapToIntExample {
public static void main(String[] args) {
List<int[]> arrayOfArrays = Arrays.asList(
new int[]{1, 2, 3},
new int[]{4, 5},
new int[]{6, 7, 8, 9}
);
// 使用 flatMapToInt 将嵌套的数组展平成一个单一的 IntStream
int sum = arrayOfArrays.stream()
.flatMapToInt(Arrays::stream)
.sum();
System.out.println(sum); // 输出: 45
}
}
flatMapToLong
介绍
flatMapToLong
方法将流中的每个元素转换为一个 long
类型的基本类型流 (LongStream
),然后将所有这些流合并成一个单一的 LongStream
。
特点
- 中间操作:
flatMapToLong
返回一个LongStream
,允许后续针对基本类型long
的操作。 - 性能优化:类似于
flatMapToInt
,flatMapToLong
可以减少装箱操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;
public class FlatMapToLongExample {
public static void main(String[] args) {
List<long[]> arrayOfArrays = Arrays.asList(
new long[]{1L, 2L, 3L},
new long[]{4L, 5L},
new long[]{6L, 7L, 8L, 9L}
);
// 使用 flatMapToLong 将嵌套的数组展平成一个单一的 LongStream
long sum = arrayOfArrays.stream()
.flatMapToLong(Arrays::stream)
.sum();
System.out.println(sum); // 输出: 45
}
}
flatMapToDouble
介绍
flatMapToDouble
方法将流中的每个元素转换为一个 double
类型的基本类型流 (DoubleStream
),然后将所有这些流合并成一个单一的 DoubleStream
。
特点
- 中间操作:
flatMapToDouble
返回一个DoubleStream
,允许后续针对基本类型double
的操作。 - 性能优化:类似于
flatMapToInt
和flatMapToLong
,flatMapToDouble
可以减少装箱操作,提高性能。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;
public class FlatMapToDoubleExample {
public static void main(String[] args) {
List<double[]> arrayOfArrays = Arrays.asList(
new double[]{1.1, 2.2, 3.3},
new double[]{4.4, 5.5},
new double[]{6.6, 7.7, 8.8, 9.9}
);
// 使用 flatMapToDouble 将嵌套的数组展平成一个单一的 DoubleStream
double sum = arrayOfArrays.stream()
.flatMapToDouble(Arrays::stream)
.sum();
System.out.println(sum); // 输出: 49.5
}
}
mapMulti
介绍
mapMulti
是一个中间操作,允许将流中的每个元素映射为零个、一个或多个新的元素。它接受一个 BiConsumer
,该消费者接收当前元素和一个 Consumer
,通过这个 Consumer
可以发射任意数量的新元素。
特点
- 灵活性高:可以生成零个、一个或多个新元素。
- 中间操作:返回一个新的流,允许后续的链式操作。
示例代码
假设我们有一个包含多个标签的字符串列表,我们希望将每个字符串拆分为单独的标签并生成新的流元素。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class MapMultiExample {
public static void main(String[] args) {
List<String> items = Arrays.asList("apple,banana", "orange", "grape,melon,kiwi");
List<String> tags = items.stream()
.flatMap(item -> Arrays.stream(item.split(","))) // 使用 flatMap 也可以实现类似功能
//.mapMulti((item, consumer) -> {
// for (String tag : item.split(",")) {
// consumer.accept(tag);
// }
//})
.collect(Collectors.toList());
// 使用 mapMulti 的方式
List<String> tagsMapMulti = items.stream()
.mapMulti((item, consumer) -> {
for (String tag : item.split(",")) {
consumer.accept(tag);
}
})
.collect(Collectors.toList());
System.out.println(tagsMapMulti); // 输出: [apple, banana, orange, grape, melon, kiwi]
}
}
注意:在上面的示例中,flatMap
已经可以实现类似的功能,但 mapMulti
提供了一种更直观的方式来生成多个输出元素。
mapMultiToInt
介绍
mapMultiToInt
是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 int
类型的值。它返回一个 IntStream
,适用于需要处理基本类型 int
的场景。
特点
- 中间操作:返回一个
IntStream
,允许后续针对基本类型int
的操作。 - 性能优化:避免了装箱操作,提高了性能。
示例代码
假设我们有一个包含多个整数的列表,每个整数可能对应多个因子,我们希望将这些因子展平成一个单一的 IntStream
。
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class MapMultiToIntExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(12, 15, 20);
IntStream factors = numbers.stream()
.mapMultiToInt(number -> {
for (int i = 1; i <= number; i++) {
if (number % i == 0) {
// 发射因子
// 注意:实际实现需要避免发射超过number的因子
if (i != number / i || number % i == 0) { // 简化条件
// 正确的方式是只发射i和number/i中的一个,避免重复
if (i <= number / i) {
// 发射因子i
// 这里只是示例,实际需要更复杂的逻辑来避免重复
// 例如,只发射i如果i*i != number
if (i * i != number) {
// 发射i和number/i
// 但mapMultiToInt不允许发射多个值,需要调整
}
// 由于mapMultiToInt每次只能发射一个值,需要分开处理
// 更好的方式是使用 flatMap 或其他方法
}
}
}
// 正确的实现应使用多个consumer.accept调用
// 但由于mapMultiToInt的限制,这里只是一个简化示例
// 实际应用中建议使用 flatMap 或其他适合的方法
});
// 由于mapMultiToInt的限制,上面的示例并不适用
// 更合适的示例应使用 flatMap 或其他方法
// 正确的示例使用 flatMapToInt
IntStream correctFactors = numbers.stream()
.flatMapToInt(number -> {
IntStream factorsStream = IntStream.empty();
for (int i = 1; i <= number; i++) {
if (number % i == 0) {
factorsStream = factorsStream.concat(IntStream.of(i));
if (i != number / i) { // 避免平方因子重复
factorsStream = factorsStream.concat(IntStream.of(number / i));
}
}
}
return factorsStream;
});
// 由于IntStream不支持concat,可以使用boxed或其他方式
// 更简单的方式是分开处理
IntStream factorsStream = numbers.stream()
.flatMapToInt(number -> IntStream.rangeClosed(1, number)
.filter(i -> number % i == 0)
);
factorsStream.forEach(System.out::println);
}
}
注意:mapMultiToInt
目前(截至Java 17)并不支持在一个映射中发射多个值,因此上面的示例并不适用。实际应用中,建议使用 flatMapToInt
来实现类似的功能。
正确的使用方式
由于 mapMultiToInt
不支持在一个映射中发射多个值,推荐使用 flatMapToInt
来实现将每个元素映射为多个 int
值的场景。
IntStream factors = numbers.stream()
.flatMapToInt(number -> IntStream.rangeClosed(1, number)
.filter(i -> number % i == 0)
);
mapMultiToLong
介绍
mapMultiToLong
是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 long
类型的值。它返回一个 LongStream
,适用于需要处理基本类型 long
的场景。
特点
- 中间操作:返回一个
LongStream
,允许后续针对基本类型long
的操作。 - 性能优化:避免了装箱操作,提高了性能。
示例代码
类似于 mapMultiToInt
,但由于 mapMultiToLong
也不支持在一个映射中发射多个值,推荐使用 flatMapToLong
。
import java.util.Arrays;
import java.util.List;
import java.util.stream.LongStream;
public class MapMultiToLongExample {
public static void main(String[] args) {
List<Long> numbers = Arrays.asList(12L, 15L, 20L);
// 使用 flatMapToLong 来发射多个 long 值
LongStream factors = numbers.stream()
.flatMapToLong(number -> LongStream.rangeClosed(1, number)
.filter(i -> number % i == 0)
);
factors.forEach(System.out::println);
}
}
mapMultiToDouble
介绍
mapMultiToDouble
是一个中间操作,专门用于将流中的元素映射为零个、一个或多个 double
类型的值。它返回一个 DoubleStream
,适用于需要处理基本类型 double
的场景。
特点
- 中间操作:返回一个
DoubleStream
,允许后续针对基本类型double
的操作。 - 性能优化:避免了装箱操作,提高了性能。
示例代码
同样,mapMultiToDouble
不支持在一个映射中发射多个值,推荐使用 flatMapToDouble
。
import java.util.Arrays;
import java.util.List;
import java.util.stream.DoubleStream;
public class MapMultiToDoubleExample {
public static void main(String[] args) {
List<Double> numbers = Arrays.asList(12.0, 15.0, 20.0);
// 使用 flatMapToDouble 来发射多个 double 值
DoubleStream factors = numbers.stream()
.flatMapToDouble(number -> DoubleStream.of(/* 这里需要生成多个double值,示例不适用 */));
// 更合适的做法是使用 flatMapToDouble 结合生成器
// 例如,发射每个数字的小数部分
DoubleStream decimalParts = numbers.stream()
.flatMapToDouble(number -> DoubleStream.of(number % 1.0));
decimalParts.forEach(System.out::println);
}
}
过滤
filter
介绍
filter
方法用于筛选流中的元素,只保留满足特定条件的元素。它接受一个 Predicate
函数作为参数,该函数对流中的每个元素进行测试,返回 true
的元素将被保留。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 筛选出所有偶数
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出: [2, 4, 6, 8, 10]
}
}
去重
distinct
介绍
distinct
方法用于去除流中的重复元素,返回一个包含唯一元素的新流。它依赖于元素的 equals
和 hashCode
方法来判断元素是否相同。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 6, 6, 7);
// 去除重复元素
List<Integer> uniqueNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNumbers); // 输出: [1, 2, 3, 4, 5, 6, 7]
}
}
排序
默认排序(自然顺序)
sorted()
方法无需参数,默认按照元素的自然顺序进行排序。这要求元素实现 Comparable
接口。
示例:对整数列表进行正序排序**
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SortedDefaultExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出: [1, 2, 3, 5, 8]
}
}
自定义排序(倒序)
你可以通过传递一个自定义的 Comparator
来实现倒序排序。使用 Comparator.reverseOrder()
可以轻松实现倒序。
示例:对字符串列表进行倒序排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedReverseExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
List<String> sortedNamesDesc = names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedNamesDesc); // 输出: [Eve, David, Charlie, Bob, Alice]
}
}
根据对象的某个属性排序
当处理对象流时,通常需要根据对象的某个属性进行排序。可以使用 Comparator.comparing
方法来实现。
示例:按年龄对人员列表进行正序排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class SortedByPropertyExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// 按年龄正序排序
List<Person> sortedByAgeAsc = people.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
System.out.println(sortedByAgeAsc); // 输出: [Bob (25), Alice (30), Charlie (35)]
}
}
多条件排序
有时需要根据多个属性进行排序,例如先按年龄排序,再按姓名排序。可以通过链式调用 thenComparing
方法来实现。
示例:先按年龄升序排序,再按姓名升序排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class MultiCriteriaSortExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 25),
new Person("David", 30)
);
// 先按年龄升序,再按姓名升序
List<Person> sortedByNameThenAge = people.stream()
.sorted(
Comparator.comparing(Person::getAge)
.thenComparing(Person::getName)
)
.collect(Collectors.toList());
System.out.println(sortedByNameThenAge);
// 输出:
// [Bob (25), Charlie (25), Alice (30), David (30)]
}
}
使用特定比较器
对于基本数据类型,可以使用 Comparator.comparingInt
、Comparator.comparingLong
或 Comparator.comparingDouble
来避免装箱操作,提高性能。
示例:按字符串长度升序排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedByLengthExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 按字符串长度升序排序
List<String> sortedByLength = names.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
System.out.println(sortedByLength); // 输出: [Bob, Eve, Alice, David, Charlie]
}
}
自定义比较器
如果需要更复杂的排序逻辑,可以自定义 Comparator
。
示例:按姓名长度降序排序,如果长度相同则按字母顺序升序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class CustomComparatorExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve", "Anna");
// 按姓名长度降序排序,如果长度相同则按字母顺序升序
List<String> customSortedNames = names.stream()
.sorted(
Comparator.comparingInt(String::length).reversed()
.thenComparing(Comparator.naturalOrder())
)
.collect(Collectors.toList());
System.out.println(customSortedNames);
// 输出: [Charlie, Alice, David, Eve, Bob, Anna]
}
}
在对象中使用多个排序条件
假设有一个更复杂的对象 Employee
,包含 department
(部门)、salary
(薪水)和 name
(姓名)属性。我们希望先按部门升序排序,再按薪水降序排序,最后按姓名升序排序。
示例:多级排序
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Employee {
private String department;
private double salary;
private String name;
public Employee(String department, double salary, String name) {
this.department = department;
this.salary = salary;
this.name = name;
}
public String getDepartment() { return department; }
public double getSalary() { return salary; }
public String getName() { return name; }
@Override
public String toString() {
return department + " - " + name + " ($" + salary + ")";
}
}
public class MultiLevelSortExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Sales", 70000, "Alice"),
new Employee("Engineering", 80000, "Bob"),
new Employee("Sales", 75000, "Charlie"),
new Employee("Engineering", 85000, "David"),
new Employee("HR", 60000, "Eve")
);
// 先按部门升序,再按薪水降序,最后按姓名升序
List<Employee> sortedEmployees = employees.stream()
.sorted(
Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName)
)
.collect(Collectors.toList());
System.out.println(sortedEmployees);
// 输出:
// Engineering - David ($85000)
// Engineering - Bob ($80000)
// HR - Eve ($60000)
// Sales - Charlie ($75000)
// Sales - Alice ($70000)
}
}
限制
limit
介绍
limit
方法用于限制流中元素的数量,返回前 n
个元素。它常用于分页或获取部分结果。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LimitExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 获取前5个元素
List<Integer> firstFive = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println(firstFive); // 输出: [1, 2, 3, 4, 5]
}
}
跳过
skip
介绍
skip
方法用于跳过流中的前 n
个元素,返回剩余的元素。它常用于分页或忽略部分数据。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SkipExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 跳过前5个元素
List<Integer> remaining = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println(remaining); // 输出: [6, 7, 8, 9, 10]
}
}
规约
reduce
介绍
reduce
方法用于对流中的元素进行聚合操作,将流中的元素组合成一个单一的结果。它可以用于计算总和、乘积、最大值、最小值等。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计算总和
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
sum.ifPresent(System.out::println); // 输出: 15
// 计算乘积
Optional<Integer> product = numbers.stream()
.reduce((a, b) -> a * b);
product.ifPresent(System.out::println); // 输出: 120
// 计算最大值
Optional<Integer> max = numbers.stream()
.reduce(Integer::max);
max.ifPresent(System.out::println); // 输出: 5
// 计算最小值
Optional<Integer> min = numbers.stream()
.reduce(Integer::min);
min.ifPresent(System.out::println); // 输出: 1
}
}
聚合
min
介绍
min
方法用于查找流中的最小值。它接受一个 Comparator
函数作为参数,该函数定义了元素之间的比较规则。如果没有提供 Comparator
,则默认使用元素的自然顺序。
示例代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class MinExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 使用自然顺序查找最小值
Optional<Integer> minNumber = numbers.stream()
.min(Comparator.naturalOrder());
minNumber.ifPresent(System.out::println); // 输出: 1
// 使用自定义比较器查找最小值(例如按绝对值)
List<Integer> withNegatives = Arrays.asList(-5, 3, -8, 1, 2);
Optional<Integer> minAbs = withNegatives.stream()
.min(Comparator.comparingInt(Math::abs));
minAbs.ifPresent(System.out::println); // 输出: 1
}
}
max
介绍
max
方法用于查找流中的最大值。类似于 min
,它也接受一个 Comparator
函数来定义比较规则,默认使用元素的自然顺序。
示例代码
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class MaxExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 使用自然顺序查找最大值
Optional<Integer> maxNumber = numbers.stream()
.max(Comparator.naturalOrder());
maxNumber.ifPresent(System.out::println); // 输出: 8
// 使用自定义比较器查找最大值(例如按绝对值)
List<Integer> withNegatives = Arrays.asList(-5, 3, -8, 1, 2);
Optional<Integer> maxAbs = withNegatives.stream()
.max(Comparator.comparingInt(Math::abs));
maxAbs.ifPresent(System.out::println); // 输出: -8
}
}
count
介绍
count
方法用于统计流中元素的数量。它返回一个 long
类型的值,表示流中的元素总数。
示例代码
import java.util.Arrays;
import java.util.List;
public class CountExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 统计元素数量
long count = names.stream()
.count();
System.out.println(count); // 输出: 5
}
}
合并
concat
介绍
concat
方法用于将两个流合并为一个流。它接受两个参数,分别是第一个流和第二个流,并返回一个新的流,其中包含两个输入流的所有元素,顺序与输入顺序一致。
示例代码
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConcatExample {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
// 使用 Stream.concat 合并两个流
Stream<Integer> combinedStream = Stream.concat(list1.stream(), list2.stream());
// 收集合并后的流到列表
List<Integer> combinedList = combinedStream.collect(Collectors.toList());
System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6]
}
}
使用Stream.concat合并多个流
如果需要合并多个流,可以嵌套使用 Stream.concat
方法:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ConcatMultipleStreamsExample {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
List<Integer> list3 = Arrays.asList(7, 8, 9);
// 合并三个流
Stream<Integer> combinedStream = Stream.concat(Stream.concat(list1.stream(), list2.stream()), list3.stream());
// 收集合并后的流到列表
List<Integer> combinedList = combinedStream.collect(Collectors.toList());
System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
使用 flatMap 合并多个流
对于合并多个流,flatMap
提供了一种更灵活的方法:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapConcatExample {
public static void main(String[] args) {
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
// 使用 flatMap 合并多个列表
List<Integer> combinedList = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(combinedList); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
集合
collect
介绍
collect
方法用于将流中的元素累积成一个结果。它是一个终端操作,意味着在调用 collect
后,流的处理将结束,不会再进行后续的操作。collect
方法接受一个 Collector
接口的实现,该接口定义了如何将流中的元素累积成期望的结果。
示例代码
import java.util.*;
import java.util.stream.Collectors;
public class CollectExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 收集到List
List<String> nameList = names.stream().collect(Collectors.toList());
System.out.println(nameList); // 输出: [Alice, Bob, Charlie, David, Eve]
// 收集到Set
Set<String> nameSet = names.stream().collect(Collectors.toSet());
System.out.println(nameSet); // 输出顺序可能不同: [Alice, Bob, Charlie, David, Eve]
}
}
toList
介绍
toList
收集器将流中的元素收集到一个 List
中。这是一个便捷的方法,等同于使用 Collectors.toList()
。
示例代码
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> numberList = numbers.stream().collect(Collectors.toList());
System.out.println(numberList); // 输出: [1, 2, 3, 4, 5]
toSet
介绍
toSet
收集器将流中的元素收集到一个 Set
中。由于 Set
不允许重复元素,因此如果流中有重复的元素,只会保留一个。
示例代码
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Set<Integer> numberSet = numbers.stream().collect(Collectors.toSet());
System.out.println(numberSet); // 输出: [1, 2, 3, 4, 5]
toMap
介绍
toMap
收集器将流中的元素收集到一个 Map
中。它需要两个函数参数,分别用于生成键和值。如果存在重复的键,可以通过第三个参数指定合并策略。
示例代码
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Map<String, Integer> nameLengthMap = names.stream()
.collect(Collectors.toMap(
name -> name, // 键生成函数
String::length // 值生成函数
));
System.out.println(nameLengthMap); // 输出: {Alice=5, Bob=3, Charlie=7}
// 处理重复键
List<String> duplicateNames = Arrays.asList("Alice", "Bob", "Alice");
Map<String, Integer> duplicateMap = duplicateNames.stream()
.collect(Collectors.toMap(
name -> name,
String::length,
(existing, replacement) -> existing // 保留现有值
));
System.out.println(duplicateMap); // 输出: {Alice=5, Bob=3}
summingInt、averagingInt、maxBy、minBy
介绍
这些收集器用于数值计算:
summingInt
:计算整数属性的总和。averagingInt
:计算整数属性的平均值。maxBy
:查找最大值。minBy
:查找最小值。
这些收集器通常与对象的某个数值属性一起使用。
示例代码
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class NumericCollectorsExample {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// 总年龄
int totalAge = people.stream()
.collect(Collectors.summingInt(Person::getAge));
System.out.println(totalAge); // 输出: 90
// 平均年龄
double averageAge = people.stream()
.collect(Collectors.averagingInt(Person::getAge));
System.out.println(averageAge); // 输出: 30.0
// 最大年龄
Optional<Person> oldestPerson = people.stream()
.collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)));
oldestPerson.ifPresent(System.out::println); // 输出: Charlie (35)
// 最小年龄
Optional<Person> youngestPerson = people.stream()
.collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)));
youngestPerson.ifPresent(System.out::println); // 输出: Bob (25)
}
}
groupingBy
介绍
groupingBy
收集器用于根据某个属性对元素进行分组。它类似于SQL中的 GROUP BY
操作。
示例代码
class Product {
private String category;
private String name;
public Product(String category, String name) {
this.category = category;
this.name = name;
}
public String getCategory() { return category; }
public String getName() { return name; }
@Override
public String toString() {
return name + " (" + category + ")";
}
}
public class GroupingByExample {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("Electronics", "Laptop"),
new Product("Electronics", "Smartphone"),
new Product("Books", "Novel"),
new Product("Books", "Textbook"),
new Product("Electronics", "Tablet")
);
// 按类别分组
Map<String, List<Product>> groupedProducts = products.stream()
.collect(Collectors.groupingBy(Product::getCategory));
System.out.println(groupedProducts);
// 输出:
// {Electronics=[Laptop (Electronics), Smartphone (Electronics), Tablet (Electronics)],
// Books=[Novel (Books), Textbook (Books)]}
}
}
进阶用法:多级分组
可以使用 Collectors.groupingBy
的嵌套来实现多级分组。
// 多级分组:先按类别,再按名称长度
Map<String, Map<Integer, List<Product>>> multiLevelGroup = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.groupingBy(p -> p.getName().length())
));
System.out.println(multiLevelGroup);
partitioningBy
介绍
partitioningBy
收集器用于根据某个布尔条件将元素分为两组。它返回一个 Map<Boolean, List<T>>
,其中键为 true
和 false
,分别对应满足和不满足条件的元素。
示例代码
public class PartitioningByExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 按是否为偶数分组
Map<Boolean, List<Integer>> partitionedByEven = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitionedByEven);
// 输出: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}
}
}
joining
介绍
joining
收集器用于将流中的字符串元素连接成一个单一的字符串。可以指定分隔符、前缀和后缀。
示例代码
public class JoiningExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("Java", "Stream", "API");
// 简单连接
String joined = words.stream().collect(Collectors.joining());
System.out.println(joined); // 输出: JavaStreamAPI
// 使用分隔符
String joinedWithComma = words.stream().collect(Collectors.joining(", "));
System.out.println(joinedWithComma); // 输出: Java, Stream, API
// 使用前缀和后缀
String joinedWithPrefixSuffix = words.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(joinedWithPrefixSuffix); // 输出: [Java, Stream, API]
}
}