「IntStreamからList<Integer>を作ろうとしたらコンパイルエラーになった」——Java Gold 1Z0-826の学習を進めていると、一度はこの壁にぶつかるのではないでしょうか。原因を調べるとboxed()というメソッドに行き着くものの、「結局いつ使えばいいのか」がはっきりしないまま、なんとなくエラーを回避するために書いている方も多いはずです。
この記事では、boxed()が必要になる場面を整理し、試験本番で見落としやすいひっかけポイントまで具体的なコード付きで解説します。読み終える頃には、IntStreamまわりの問題で迷わなくなります。
boxedとは何か
boxed()は、IntStream・LongStream・DoubleStreamといったプリミティブ型専用のストリームを、対応するラッパー型のストリームに変換するメソッドです。
IntStream→boxed()→Stream<Integer>LongStream→boxed()→Stream<Long>DoubleStream→boxed()→Stream<Double>
内部的にはオートボクシング(プリミティブ型をラッパー型に自動変換する仕組み)を明示的に行っているだけで、特別な計算をしているわけではありません。
IntStream intStream = IntStream.rangeClosed(1, 5);
Stream<Integer> stream = intStream.boxed();
stream.forEach(System.out::println);
// 1
// 2
// 3
// 4
// 5なぜプリミティブ型専用のStreamが存在するのか
そもそもJavaにIntStreamのようなプリミティブ型専用のストリームが用意されているのは、パフォーマンスのためです。Stream<Integer>はint値を毎回Integerオブジェクトに変換して保持しますが、これはオートボクシングのコストがかかります。大量データを扱う集計処理では、このコストが無視できません。
そのためsum()・average()・max()・min()といった集計系メソッドは、IntStream側に直接用意されており、boxed()をする必要がありません。
int total = IntStream.rangeClosed(1, 100).sum();
System.out.println(total); // 5050
OptionalDouble avg = IntStream.rangeClosed(1, 100).average();
System.out.println(avg.getAsDouble()); // 50.5「集計するならとりあえずboxed()」と覚えていると、この時点で無駄な変換を書いてしまうミスにつながります。
boxedが必要になる代表的な場面
boxed()が必要になるのは、主に「Stream<T>を前提としたAPIを使いたいとき」です。代表例は次の3つです。
1. Collectorsでコレクションを作りたいとき
IntStreamにはcollect(Collector)のオーバーロードが存在しません。Collectors.toList()などを使うには、事前にboxed()でStream<Integer>に変換する必要があります。
List<Integer> list = IntStream.rangeClosed(1, 5)
.boxed()
.collect(Collectors.toList());
System.out.println(list); // [1, 2, 3, 4, 5]2. Comparatorを使ってソートしたいとき
IntStreamのsorted()は引数なしで自然順にソートするメソッドしか持ちません。Comparatorを渡して降順にしたい場合などは、boxed()が必須です。
List<Integer> desc = IntStream.rangeClosed(1, 5)
.boxed()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(desc); // [5, 4, 3, 2, 1]3. groupingByなどでグループ化したいとき
Collectors.groupingBy()はStream<T>にしか使えないため、IntStreamのままでは呼び出せません。
Map<Boolean, List<Integer>> grouped = IntStream.rangeClosed(1, 10)
.boxed()
.collect(Collectors.partitioningBy(i -> i % 2 == 0));
System.out.println(grouped);
// {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}boxedを使わないとどうなるか
実際にコンパイルエラーになる例を見てみましょう。
List<Integer> list = IntStream.rangeClosed(1, 5)
.collect(Collectors.toList()); // コンパイルエラーIntStreamが持つcollectは次のシグネチャです。
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner)Collectors.toList()が返すCollectorオブジェクトは、この3引数版のシグネチャに一致しないため、コンパイルが通りません。「collectがあるからCollectorsが使えるはず」という思い込みが、このエラーの典型的な原因です。
boxedとmapToObjの違い
もう一つ、試験で狙われやすいのがboxed()とmapToObj()の違いです。boxed()は「ラッパー型への変換専用」であるのに対し、mapToObj()は任意の型への変換に使えます。
List<String> strList = IntStream.rangeClosed(1, 3)
.mapToObj(i -> "No." + i)
.collect(Collectors.toList());
System.out.println(strList); // [No.1, No.2, No.3]ここで注意したいのが、文字列連結を行うCollectors.joining()です。joining()はCharSequence型の要素を前提としているため、boxed()で得られるStream<Integer>には使えません。
// 誤り:Integerはjoiningの対象外でコンパイルエラー
String joined = IntStream.rangeClosed(1, 3)
.boxed()
.collect(Collectors.joining(", "));正しくはmapToObj(String::valueOf)で文字列に変換してからjoining()を呼び出します。
String joined = IntStream.rangeClosed(1, 3)
.mapToObj(String::valueOf)
.collect(Collectors.joining(", "));
System.out.println(joined); // 1, 2, 3「boxed()さえ呼べば後は何でもできる」という誤解が、この種の問題の失点ポイントになります。
IntStreamとStream<Integer>の操作比較
| 操作 | IntStreamのまま呼べるか | boxedが必要か |
|---|---|---|
| sum() / average() / max() / min() | ○ 呼べる | 不要 |
| sorted()(自然順) | ○ 呼べる | 不要 |
| sorted(Comparator) | × 呼べない | 必要 |
| distinct() | ○ 呼べる | 不要 |
| collect(Collectors.toList()等) | × 呼べない | 必要 |
| collect(Collectors.groupingBy等) | × 呼べない | 必要 |
| collect(Collectors.joining()) | × 呼べない | mapToObjが必要(boxedでは不可) |
試験で問われやすい注意点まとめ
sum()・average()など集計系メソッドはIntStreamのままで完結し、boxed()は不要です。sorted()は引数なしならIntStreamのままで呼べますが、Comparatorを渡す場合はboxed()が必要です。Collectors系のメソッド(toList()・groupingBy()・partitioningBy()など)を使う場合は、必ず事前にboxed()が必要です。Collectors.joining()だけはboxed()では対応できず、mapToObj(String::valueOf)のように文字列へ変換する必要があります。Stream.of(1, 2, 3)は最初からStream<Integer>であり、IntStream.of(1, 2, 3)とは型が異なります。両者を混同しないよう注意してください。boxed()はオートボクシングを伴うため、大量データの処理では性能面のトレードオフがある、という理解も問われることがあります。
まとめ
boxed()は「IntStreamなどのプリミティブ型ストリームを、CollectorsやComparatorといったStream<T>前提のAPIに橋渡しするための変換」です。集計処理では不要、コレクション化やソート、グループ化では必要、という基準を押さえておけば、試験本番でも迷わず判断できるようになります。特にjoining()のようにboxed()では対応できない例外があることまで押さえておくと、応用問題にも対応しやすくなります。