「Stream<Integer>を使えばいいのに、なぜわざわざIntStreamが存在するの?」
「mapToIntとmap、何が違うのか結局よく分からない」
「average()の戻り値、Optionalなの?OptionalDoubleなの?」

Java Gold(1Z0-826)の学習を進めていると、Stream APIのプリミティブ型特化バージョンで必ずつまずくポイントがあります。特にIntStream・LongStream・DoubleStreamは、似ているようで微妙に仕様が異なるため、試験でも狙われやすい範囲です。

結論から言うと、これらは「ボクシング・アンボクシングのコストを避けるために用意された専用Stream」であり、使い分けの基準さえ押さえれば混乱することはありません。この記事では、それぞれの役割の違い、主要メソッドの比較、そして試験でひっかかりやすいポイントを整理して解説します。読み終える頃には、この分野の問題に自信を持って答えられるようになります。

そもそもなぜプリミティブ型専用のStreamが必要なのか

Stream<Integer>のように、ラッパークラス(IntegerやLong、Double)をジェネリクスで扱うStreamは確かに存在します。しかし、大量のデータを処理する場合、これには無視できないコストが発生します。

  • int型の値をIntegerオブジェクトに変換する処理を「ボクシング」と呼ぶ
  • Integerオブジェクトをint型に戻す処理を「アンボクシング」と呼ぶ
  • 大量データに対してこの変換が繰り返されると、メモリ効率や処理速度が悪化する

そこでJavaは、int・long・doubleというプリミティブ型を直接扱える専用のStreamとして、IntStream・LongStream・DoubleStreamを用意しました。オブジェクトへの変換が発生しないため、数値計算を伴う処理では基本的にこちらを優先して使います。

// Stream<Integer>の場合(ボクシングが発生)
Stream<Integer> boxedStream = Stream.of(1, 2, 3);

// IntStreamの場合(プリミティブ型のまま処理)
IntStream intStream = IntStream.of(1, 2, 3);

IntStream・LongStream・DoubleStreamの基本的な使い分け

3つのStreamは、扱うプリミティブ型に応じて役割が分かれています。

Stream種別扱う型主な用途
IntStreamint配列のインデックス操作、カウント処理、整数の集計
LongStreamlong大きな整数値の集計(ID採番、ファイルサイズ計算など)
DoubleStreamdouble平均値や小数を含む統計計算

生成方法にもそれぞれ専用のstaticメソッドが用意されています。

IntStream.range(1, 5);          // 1,2,3,4(5は含まない)
IntStream.rangeClosed(1, 5);    // 1,2,3,4,5(5を含む)
LongStream.of(100L, 200L, 300L);
DoubleStream.of(1.5, 2.5, 3.5);

注意点: rangeとrangeClosedはIntStreamとLongStreamにのみ存在し、DoubleStreamには存在しません。小数の範囲は連続値として定義できないため、DoubleStreamにはrange系メソッドがない、という点は試験でよく問われます。

通常のStreamとの相互変換:map系メソッドの使い分け

Stream<T>とプリミティブ型Streamを行き来する場面では、複数の変換メソッドが登場します。この使い分けが1Z0-826の頻出ポイントです。

メソッド変換前変換後
mapToIntStream<T>などIntStream
mapToLongStream<T>などLongStream
mapToDoubleStream<T>などDoubleStream
mapToObjIntStream/LongStream/DoubleStreamStream<T>
boxedIntStream/LongStream/DoubleStreamStream<Integer>など対応するラッパー型のStream
List<String> words = List.of("apple", "banana", "kiwi");

// 各文字列の長さをIntStreamに変換
IntStream lengths = words.stream().mapToInt(String::length);

// IntStreamをStream<Integer>に戻す(boxed)
Stream<Integer> boxedLengths = lengths.boxed();

// IntStreamを任意のオブジェクト型Streamに変換(mapToObj)
IntStream.rangeClosed(1, 3)
    .mapToObj(i -> "No." + i)
    .forEach(System.out::println);
// 出力:
// No.1
// No.2
// No.3

ひっかけポイント: boxed()は必ず対応するラッパークラスのStreamに変換します(IntStream→Stream<Integer>)。一方mapToObj()は、ラムダ式の戻り値次第でどんな型のStreamにもなれます。この違いを問う選択肢は非常に多く出題されます。

集計メソッドの戻り値:Optional系の違いに要注意

IntStream・LongStream・DoubleStreamには、合計・平均・最大・最小を求める集計メソッドが用意されています。ここで最も間違えやすいのが「戻り値の型」です。

メソッド戻り値の型補足
sum()int / long / doubleOptionalではなく直接プリミティブ値を返す
average()OptionalDoubleStream種別に関わらず必ずOptionalDouble
max() / min()OptionalInt / OptionalLong / OptionalDouble元のStream種別に対応したOptional型
count()longStream共通、常にlong型
IntStream numbers = IntStream.of(2, 4, 6, 8);

int total = numbers.sum();
// total = 20(Optionalではなくintそのもの)

OptionalDouble avg = IntStream.of(2, 4, 6, 8).average();
System.out.println(avg.getAsDouble());
// 出力: 5.0

試験で狙われる最大のポイント:
IntStreamやLongStreamに対してaverage()を呼んでも、戻り値はOptionalIntやOptionalLongではなく、必ずOptionalDoubleになります。平均値は割り算の結果なので小数になる可能性があるためです。これをOptionalIntと誤答する選択肢は頻出なので、必ず覚えておきましょう。

反対に、max()やmin()は元の型のOptionalがそのまま返ります。

OptionalInt max = IntStream.of(2, 4, 6, 8).max();
System.out.println(max.getAsInt());
// 出力: 8

summaryStatistics()で複数の集計値を一度に取得する

個別にsum()やaverage()を呼ぶ代わりに、まとめて統計値を取得できるメソッドもあります。

IntSummaryStatistics stats = IntStream.of(1, 2, 3, 4, 5)
    .summaryStatistics();

System.out.println(stats.getSum());     // 出力: 15
System.out.println(stats.getAverage()); // 出力: 3.0
System.out.println(stats.getMax());     // 出力: 5
System.out.println(stats.getMin());     // 出力: 1
System.out.println(stats.getCount());   // 出力: 5

対応するクラスはStream種別ごとに異なります。

Stream種別統計クラス
IntStreamIntSummaryStatistics
LongStreamLongSummaryStatistics
DoubleStreamDoubleSummaryStatistics

注意点: これらのクラス名を混同する誤答選択肢がよく出ます。「IntStreamからLongSummaryStatisticsを受け取る」ようなコードはコンパイルエラーになるため、型の対応関係を正確に覚えておく必要があります。

まとめ:使い分けの判断基準を整理する

IntStream・LongStream・DoubleStreamは、扱うデータの性質によって明確に使い分けます。

  • 整数のカウントやインデックス操作 → IntStream
  • 大きな数値やID計算 → LongStream
  • 平均や小数を伴う統計処理 → DoubleStream
  • オブジェクト型との相互変換は、mapToInt/mapToLong/mapToDouble(変換)とboxed/mapToObj(戻す)を使い分ける
  • average()は常にOptionalDoubleを返す(最重要ポイント)
  • sum()はOptionalを経由せず、プリミティブ値をそのまま返す

これらの違いは単なる暗記ではなく、「なぜその型が必要なのか」という設計思想を理解しておくと、初見の問題にも応用が効きます。次回は、Stream APIのreduceメソッドについて、初期値の有無による挙動の違いを解説していきます。