Java Stream boxed 方法
上次修改时间:2025 年 6 月 1 日
Java Streams 中的 boxed 方法是在处理原始类型流时的一个关键操作。本教程解释了它的目的、用例,并提供了实际示例。
Stream.boxed 是一个中间操作,它将原始类型流 (IntStream、LongStream 或 DoubleStream) 转换为相应包装器对象的 Stream (Stream<Integer>、Stream<Long> 或 Stream<Double>)。此操作称为“装箱” - 将原始值转换为其对象等效值。
当您需要使用需要对象流而不是原始类型流的 API,或者当您需要使用原始类型流上不可用的操作时,boxed 操作特别有用。
原始类型流与对象流
Java 区分原始类型流 (IntStream、LongStream、DoubleStream) 和对象流 (Stream<T>)。原始类型流针对性能和内存使用进行了优化,但有时您需要将它们转换为对象流才能使用某些 API 或操作。 boxed 方法通过将原始值转换为其对应的包装器对象来弥合这一差距。
| 原始类型流 | 装箱等效项 | 装箱方法 | 拆箱方法 |
|---|---|---|---|
| IntStream | Stream<Integer> | boxed() | mapToInt(Integer::intValue) |
| LongStream | Stream<Long> | boxed() | mapToLong(Long::longValue) |
| DoubleStream | Stream<Double> | boxed() | mapToDouble(Double::doubleValue) |
boxed 方法用于将原始类型流转换为其对象等效项,从而允许您使用标准 Stream 操作,这些操作在原始类型流上不可用。相反,拆箱方法(如 mapToInt、mapToLong 和 mapToDouble)将对象流转换回原始类型流,以用于对性能敏感的操作。
基本 boxed 用法
此示例演示了 boxed 方法的最基本用法,用于将 IntStream 转换为 Stream<Integer>。
void main() {
IntStream.range(1, 5)
.boxed() // Convert IntStream to Stream<Integer>
.forEach(System.out::println);
}
在这里,我们创建一个包含值 1 到 4 的 IntStream,然后使用 boxed 将其转换为 Stream<Integer>。这使我们能够使用标准 Stream 操作,这些操作在原始类型流上不可用。
$ java Main.java 1 2 3 4
收集原始类型流
boxed 的一个常见用例是当您需要将原始类型流元素收集到无法存储原始值的集合中时。
void main() {
List<Integer> numbers = IntStream.of(10, 20, 30)
.boxed()
.toList();
System.out.println(numbers);
}
由于 Collections 只能保存对象,因此我们需要在将原始 int 值收集到 List 中之前对其进行装箱。 boxed 操作会自动执行此转换。
$ java Main.java [10, 20, 30]
与 Stream 操作一起使用
某些 Stream 操作仅在对象流上可用。此示例显示了使用 boxed 来启用这些操作。
void main() {
DoubleStream.of(1.1, 2.2, 3.3)
.boxed() // Convert to Stream<Double>
.sorted((a, b) - b.compareTo(a)) // Reverse sort
.forEach(System.out::println);
}
带有自定义比较器的 sorted 方法在 DoubleStream 上不可用,因此我们首先使用 boxed 转换为 Stream<Double>。这使我们可以访问所有 Stream 操作。
$ java Main.java 3.3 2.2 1.1
Boxed 用于分组和收集
当您想使用需要对象的收集器来分组或收集原始值时,boxed 方法非常有用,例如按偶数/奇数对数字进行分组。
void main() {
Map<String, List<Integer>> grouped = IntStream.rangeClosed(1, 10)
.boxed()
.collect(Collectors.groupingBy(n -> n % 2 == 0 ? "Even" : "Odd"));
System.out.println(grouped);
}
此示例使用 Collectors.groupingBy 将 1 到 10 的数字分组为偶数和奇数类别。由于收集器使用对象,因此必须使用 boxed 转换原始类型流。
与其他流组合
当您需要将原始类型流与对象流组合时,boxed 变得至关重要。
void main() {
Stream.concat(
IntStream.range(1, 4).boxed(),
Stream.of("a", "b", "c")
).forEach(System.out::println);
}
Stream.concat 方法要求两个参数的类型相同 (Stream<T>)。我们使用 boxed 将 IntStream 转换为 Stream<Integer>,以便它可以与 Stream<String> 连接。
$ java Main.java 1 2 3 a b c
性能注意事项
虽然 boxed 很方便,但由于对象创建,它会影响性能。此示例演示了开销。
void main() {
long start, end;
// Without boxing
start = System.nanoTime();
int sum1 = IntStream.range(0, 1_000_000).sum();
end = System.nanoTime();
System.out.println(sum1);
System.out.println("Primitive sum: " + (end - start) + " ns");
// With boxing
start = System.nanoTime();
int sum2 = IntStream.range(0, 1_000_000)
.boxed()
.reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println(sum2);
System.out.println("Boxed sum: " + (end - start) + " ns");
}
装箱版本需要创建 1,000,000 个 Integer 对象,然后将它们转换回原始类型以进行求和。原始类型版本直接在原始类型值上操作,而没有此开销。
$ java Main.java 1783293664 Primitive sum: 15442200 ns 1783293664 Boxed sum: 61258700 ns
boxed 的替代方案
在某些情况下,mapToObj 可以用作 boxed 的替代方案,尤其是在您需要执行其他转换时。
void main() {
IntStream.range(0, 5)
.mapToObj(i - "Item " + i) // Alternative to boxed
.forEach(System.out::println);
}
在这里,我们使用 mapToObj 将 int 值转换为对象并将它们转换为字符串。当您需要两个操作时,这比使用 boxed 后跟 map 更有效。
$ java Main.java Item 0 Item 1 Item 2 Item 3 Item 4
使用 Optional
在处理来自原始类型流的 Optional 结果时,boxed 有助于转换为包装器类型的 Optional。
void main() {
OptionalDouble avg = DoubleStream.of(1.5, 2.5, 3.5).average();
// Convert OptionalDouble to Optional<Double>
Optional<Double> boxedAvg = avg.stream().boxed().findFirst();
boxedAvg.ifPresent(d - System.out.println("Average: " + d));
}
原始类型流终端操作返回 Optional 变体 (OptionalInt、OptionalLong、OptionalDouble)。当需要时,boxed 方法有助于将这些转换为常规 Optional<T>。
$ java Main.java Average: 2.5
来源
boxed 方法是在使用 Java Streams 时必不可少的工具,尤其是在需要在原始类型流和对象流之间建立桥梁时。虽然它引入了一些开销,但它可以实现强大的流操作,否则使用原始类型流是不可能的。
作者
列出所有Java教程。