IntStream とは、int の値を順番に流して処理するための、int 専用のストリームです。 IntStream.range(0, 5) と書くと 0, 1, 2, 3, 4 が順に流れ、forEach でくり返したり、sum で合計したり、boxed や mapToObj で List に変えたりできます。for文で書いていた「0 から N−1 までのくり返し」を、メソッドをつなげた形で書けるのが特徴です。

この記事では、まずメソッドの早見表を示し、続いて for文の典型12パターンを for版と IntStream版で並べ、実行結果をすべて載せます。boxed を忘れたときに出るコンパイルエラー文も実際に再現して直し方を示し、最後に Java 25 で for文と実行時間を簡易計測した結果から、どちらを選ぶかの目安をまとめます。

確認環境:Ubuntu 24.04 / OpenJDK 21.0.10 と OpenJDK 25.0.4.1(java ファイル名.java の単一ファイル実行)。掲載した出力とエラー文は、すべてこの環境で実行して得たものです。

IntStreamメソッドの早見表

作る

書き方 流れる値 メモ
IntStream.range(1, 5) 1, 2, 3, 4 終わりの値を含まない。for (int i = 1; i < 5; i++) と同じ
IntStream.rangeClosed(1, 5) 1, 2, 3, 4, 5 終わりの値を含む。for (int i = 1; i <= 5; i++) と同じ
IntStream.range(5, 1) なし(空) 開始が終了以上なら空。エラーにはならない
IntStream.iterate(0, i -> i <= 20, i -> i + 5) 0, 5, 10, 15, 20 初期値・続ける条件・次の値。Java 9以降
IntStream.iterate(1, i -> i * 2).limit(8) 1, 2, 4, …, 128 2引数は終わりがないので limit で件数を決める
IntStream.of(3, 1, 2) 3, 1, 2 値を直接並べる
Arrays.stream(int配列) 配列の要素 int[] から作る
list.stream().mapToInt(Integer::intValue) List の要素 Integer の List から作る
"abc".chars() 97, 98, 99 文字列の各文字のコード

変換する

書き方 戻り値の型 使いどころ
boxed() Integer のストリーム List・Set・Map に集める前に呼ぶ
mapToObj(i -> "No." + i) 好きな型のストリーム int から文字列やオブジェクトを作る
map(i -> i * 10) IntStream int から int への変換
mapToLong(...) / asLongStream() LongStream 合計が int の範囲を超えるとき
mapToDouble(...) DoubleStream 小数で計算したいとき
filter(i -> i % 3 == 0) IntStream 条件に合う値だけ残す
toArray() int[] 配列で受け取る

集計する

書き方 戻り値 実行結果の例
sum() int rangeClosed(1, 10).sum() は 55。空なら 0
count() long 10
max() / min() OptionalInt OptionalInt[9]。空なら OptionalInt.empty
average() OptionalDouble OptionalDouble[5.333333333333333]
summaryStatistics() IntSummaryStatistics 件数・合計・最小・平均・最大を一度に得る
anyMatch(...) / allMatch(...) boolean 条件に合う値があるか/すべて合うか

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java IntStreamの使い方|range・boxed・mapToObj」

早見表を確かめたコードと実行結果

Table.java という名前で保存し、java Table.java で動きます。

import java.util.*;
import java.util.stream.*;

public class Table {
    public static void main(String[] args) {
        System.out.println("range(1, 5)        : " + Arrays.toString(IntStream.range(1, 5).toArray()));
        System.out.println("rangeClosed(1, 5)  : " + Arrays.toString(IntStream.rangeClosed(1, 5).toArray()));
        System.out.println("range(5, 1)        : " + Arrays.toString(IntStream.range(5, 1).toArray()));
        System.out.println("range(3, 3)        : " + Arrays.toString(IntStream.range(3, 3).toArray()));
        System.out.println("rangeClosed(3, 3)  : " + Arrays.toString(IntStream.rangeClosed(3, 3).toArray()));
        System.out.println("range(-2, 2)       : " + Arrays.toString(IntStream.range(-2, 2).toArray()));
        System.out.println("of(3, 1, 2)        : " + Arrays.toString(IntStream.of(3, 1, 2).toArray()));
        System.out.println("iterate 3引数      : " + Arrays.toString(IntStream.iterate(0, i -> i <= 20, i -> i + 5).toArray()));
        System.out.println("iterate 2引数+limit: " + Arrays.toString(IntStream.iterate(1, i -> i * 2).limit(8).toArray()));
        System.out.println("boxed              : " + IntStream.range(0, 3).boxed().toList());
        System.out.println("mapToObj           : " + IntStream.range(0, 3).mapToObj(i -> "No." + i).toList());
        System.out.println("map                : " + Arrays.toString(IntStream.range(0, 3).map(i -> i * 10).toArray()));
        System.out.println("filter             : " + Arrays.toString(IntStream.range(0, 10).filter(i -> i % 3 == 0).toArray()));
        System.out.println("sum                : " + IntStream.rangeClosed(1, 10).sum());
        System.out.println("count              : " + IntStream.rangeClosed(1, 10).count());
        System.out.println("max                : " + IntStream.of(3, 9, 4).max());
        System.out.println("min                : " + IntStream.of(3, 9, 4).min().getAsInt());
        System.out.println("average            : " + IntStream.of(3, 9, 4).average());
        System.out.println("空のmax            : " + IntStream.empty().max());
        System.out.println("空のsum            : " + IntStream.empty().sum());
        IntSummaryStatistics st = IntStream.of(120, 300, 80, 450).summaryStatistics();
        System.out.println("summaryStatistics  : " + st);
        System.out.println("  個別に取り出す   : " + st.getCount() + " / " + st.getSum() + " / " + st.getMin() + " / " + st.getMax() + " / " + st.getAverage());
        System.out.println("anyMatch           : " + IntStream.range(0, 5).anyMatch(i -> i == 3));
        System.out.println("allMatch           : " + IntStream.range(0, 5).allMatch(i -> i < 3));
        System.out.println("sorted             : " + Arrays.toString(IntStream.of(3, 1, 2).sorted().toArray()));
        System.out.println("distinct           : " + Arrays.toString(IntStream.of(1, 1, 2, 2).distinct().toArray()));
        System.out.println("skip + limit       : " + Arrays.toString(IntStream.range(0, 10).skip(2).limit(3).toArray()));
        System.out.println("takeWhile          : " + Arrays.toString(IntStream.range(0, 10).takeWhile(i -> i < 4).toArray()));
        System.out.println("asLongStream.sum   : " + IntStream.rangeClosed(1, 100_000).asLongStream().sum());
        System.out.println("intのままsum       : " + IntStream.rangeClosed(1, 100_000).sum());
        System.out.println("mapToLong 2乗の和  : " + IntStream.rangeClosed(1, 100_000).mapToLong(i -> (long) i * i).sum());
        System.out.println("mapToDouble        : " + Arrays.toString(IntStream.range(1, 4).mapToDouble(i -> i / 2.0).toArray()));
        System.out.println("chars              : " + "abc".chars().boxed().toList());
        System.out.println("charsを文字に      : " + "abc".chars().mapToObj(c -> String.valueOf((char) c)).toList());
        System.out.println("Arrays.stream      : " + Arrays.stream(new int[]{4, 5, 6}).sum());
        System.out.println("List→IntStream    : " + List.of(4, 5, 6).stream().mapToInt(Integer::intValue).sum());
    }
}

実行結果:

range(1, 5)        : [1, 2, 3, 4]
rangeClosed(1, 5)  : [1, 2, 3, 4, 5]
range(5, 1)        : []
range(3, 3)        : []
rangeClosed(3, 3)  : [3]
range(-2, 2)       : [-2, -1, 0, 1]
of(3, 1, 2)        : [3, 1, 2]
iterate 3引数      : [0, 5, 10, 15, 20]
iterate 2引数+limit: [1, 2, 4, 8, 16, 32, 64, 128]
boxed              : [0, 1, 2]
mapToObj           : [No.0, No.1, No.2]
map                : [0, 10, 20]
filter             : [0, 3, 6, 9]
sum                : 55
count              : 10
max                : OptionalInt[9]
min                : 3
average            : OptionalDouble[5.333333333333333]
空のmax            : OptionalInt.empty
空のsum            : 0
summaryStatistics  : IntSummaryStatistics{count=4, sum=950, min=80, average=237.500000, max=450}
  個別に取り出す   : 4 / 950 / 80 / 450 / 237.5
anyMatch           : true
allMatch           : false
sorted             : [1, 2, 3]
distinct           : [1, 2]
skip + limit       : [2, 3, 4]
takeWhile          : [0, 1, 2, 3]
asLongStream.sum   : 5000050000
intのままsum       : 705082704
mapToLong 2乗の和  : 333338333350000
mapToDouble        : [0.5, 1.0, 1.5]
chars              : [97, 98, 99]
charsを文字に      : [a, b, c]
Arrays.stream      : 15
List→IntStream    : 15

rangeとrangeClosedの違い:終わりの値を含むかどうか

range は終わりの値を含まず、rangeClosed は含みます。 for文の < と <= の違いにそのまま対応します。

使い分けの目安は次のとおりです。

  • 配列や List のインデックスには range(0 から size の手前まで)
  • 「1 から N まで」のように人が数える範囲には rangeClosed

実行結果のとおり、range(3, 3) は空、rangeClosed(3, 3) は 3 だけが流れます。range(5, 1) のように開始のほうが大きい場合も空になり、逆順には流れません。逆順にしたいときは、次の章のパターン4の書き方を使います。

for文の典型12パターンを IntStream で書く

for版と IntStream版を並べた対応表です。

番号 やりたいこと for版 IntStream版
1 N回くり返す for (int i = 0; i < 3; i++) IntStream.range(0, 3).forEach(i -> ...)
2 1 から N まで for (int i = 1; i <= 5; i++) IntStream.rangeClosed(1, 5).forEach(i -> ...)
3 インデックス付きループ for (int i = 0; i < names.size(); i++) IntStream.range(0, names.size()).forEach(i -> ... names.get(i))
4 逆順 for (int i = 4; i >= 0; i--) IntStream.range(0, 5).map(i -> 4 - i) または IntStream.iterate(4, i -> i >= 0, i -> i - 1)
5 2刻み for (int i = 0; i < 10; i += 2) IntStream.iterate(0, i -> i < 10, i -> i + 2) または IntStream.range(0, 5).map(i -> i * 2)
6 合計 変数に sum += i IntStream.rangeClosed(1, 100).sum()
7 条件に合うものだけ ループの中に if filter(i -> i % 2 == 0)
8 List を作る 空の List に add mapToObj(i -> "ID-" + i).toList()
9 文字列のくり返し StringBuilder に append mapToObj(i -> "ab").collect(Collectors.joining())
10 2重ループ for の中に for forEach の中に forEach、または boxed().flatMap(...)
11 配列を埋める squares[i] = i * i IntStream.range(0, 5).map(i -> i * i).toArray()
12 最大値の位置を探す 位置を変数で覚える IntStream.range(0, prices.length).reduce((a, b) -> ...)

12パターンを1つのファイルにまとめました。各パターンの1行目が for版、2行目以降が IntStream版の出力です。

import java.util.*;
import java.util.stream.*;

public class Patterns {
    public static void main(String[] args) {
        List<String> names = List.of("佐藤", "鈴木", "高橋");
        int[] prices = {120, 300, 80, 450};

        System.out.println("--- 1 N回くり返す");
        for (int i = 0; i < 3; i++) { System.out.print(i + " "); }
        System.out.println();
        IntStream.range(0, 3).forEach(i -> System.out.print(i + " "));
        System.out.println();

        System.out.println("--- 2 1からNまで(終わりを含む)");
        for (int i = 1; i <= 5; i++) { System.out.print(i + " "); }
        System.out.println();
        IntStream.rangeClosed(1, 5).forEach(i -> System.out.print(i + " "));
        System.out.println();

        System.out.println("--- 3 インデックス付きループ");
        for (int i = 0; i < names.size(); i++) { System.out.print(i + ":" + names.get(i) + " "); }
        System.out.println();
        IntStream.range(0, names.size()).forEach(i -> System.out.print(i + ":" + names.get(i) + " "));
        System.out.println();

        System.out.println("--- 4 逆順");
        for (int i = 4; i >= 0; i--) { System.out.print(i + " "); }
        System.out.println();
        IntStream.range(0, 5).map(i -> 4 - i).forEach(i -> System.out.print(i + " "));
        System.out.println();
        IntStream.iterate(4, i -> i >= 0, i -> i - 1).forEach(i -> System.out.print(i + " "));
        System.out.println();

        System.out.println("--- 5 2刻み");
        for (int i = 0; i < 10; i += 2) { System.out.print(i + " "); }
        System.out.println();
        IntStream.iterate(0, i -> i < 10, i -> i + 2).forEach(i -> System.out.print(i + " "));
        System.out.println();
        IntStream.range(0, 5).map(i -> i * 2).forEach(i -> System.out.print(i + " "));
        System.out.println();

        System.out.println("--- 6 合計");
        int sum = 0;
        for (int i = 1; i <= 100; i++) { sum += i; }
        System.out.println(sum);
        System.out.println(IntStream.rangeClosed(1, 100).sum());

        System.out.println("--- 7 条件に合うものだけ");
        List<Integer> evens = new ArrayList<>();
        for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { evens.add(i); } }
        System.out.println(evens);
        System.out.println(IntStream.rangeClosed(1, 10).filter(i -> i % 2 == 0).boxed().toList());

        System.out.println("--- 8 Listを作る");
        List<String> ids = new ArrayList<>();
        for (int i = 1; i <= 3; i++) { ids.add("ID-" + i); }
        System.out.println(ids);
        System.out.println(IntStream.rangeClosed(1, 3).mapToObj(i -> "ID-" + i).toList());

        System.out.println("--- 9 文字列のくり返し");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 3; i++) { sb.append("ab"); }
        System.out.println(sb);
        System.out.println(IntStream.range(0, 3).mapToObj(i -> "ab").collect(Collectors.joining()));
        System.out.println("ab".repeat(3));

        System.out.println("--- 10 2重ループ");
        for (int i = 1; i <= 2; i++) {
            for (int j = 1; j <= 3; j++) { System.out.print(i + "x" + j + "=" + (i * j) + " "); }
        }
        System.out.println();
        IntStream.rangeClosed(1, 2).forEach(i ->
            IntStream.rangeClosed(1, 3).forEach(j -> System.out.print(i + "x" + j + "=" + (i * j) + " ")));
        System.out.println();
        System.out.println(IntStream.rangeClosed(1, 2).boxed()
            .flatMap(i -> IntStream.rangeClosed(1, 3).mapToObj(j -> i + "x" + j + "=" + (i * j)))
            .toList());

        System.out.println("--- 11 配列を埋める");
        int[] squares = new int[5];
        for (int i = 0; i < squares.length; i++) { squares[i] = i * i; }
        System.out.println(Arrays.toString(squares));
        System.out.println(Arrays.toString(IntStream.range(0, 5).map(i -> i * i).toArray()));

        System.out.println("--- 12 最大値の位置を探す");
        int maxIdx = 0;
        for (int i = 1; i < prices.length; i++) { if (prices[i] > prices[maxIdx]) { maxIdx = i; } }
        System.out.println(maxIdx);
        System.out.println(IntStream.range(0, prices.length).reduce((a, b) -> prices[a] >= prices[b] ? a : b).getAsInt());
    }
}

実行結果(for版と IntStream版が同じ出力になっています):

--- 1 N回くり返す
0 1 2 
0 1 2 
--- 2 1からNまで(終わりを含む)
1 2 3 4 5 
1 2 3 4 5 
--- 3 インデックス付きループ
0:佐藤 1:鈴木 2:高橋 
0:佐藤 1:鈴木 2:高橋 
--- 4 逆順
4 3 2 1 0 
4 3 2 1 0 
4 3 2 1 0 
--- 5 2刻み
0 2 4 6 8 
0 2 4 6 8 
0 2 4 6 8 
--- 6 合計
5050
5050
--- 7 条件に合うものだけ
[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10]
--- 8 Listを作る
[ID-1, ID-2, ID-3]
[ID-1, ID-2, ID-3]
--- 9 文字列のくり返し
ababab
ababab
ababab
--- 10 2重ループ
1x1=1 1x2=2 1x3=3 2x1=2 2x2=4 2x3=6 
1x1=1 1x2=2 1x3=3 2x1=2 2x2=4 2x3=6 
[1x1=1, 1x2=2, 1x3=3, 2x1=2, 2x2=4, 2x3=6]
--- 11 配列を埋める
[0, 1, 4, 9, 16]
[0, 1, 4, 9, 16]
--- 12 最大値の位置を探す
3
3

パターンごとの補足

番号 補足
3 インデックスが不要なら names.forEach(...) や拡張for文のほうが短い。IntStream が役立つのは、番号を表示したい・2つのリストを同じ位置で対応させたい場合
4 range(0, 5).map(i -> 4 - i) は「末尾の位置 − i」で逆順にする書き方。iterate の3引数は for文の3つの部分(初期値・条件・更新)にそのまま対応する
5 iterate は刻み幅を自由に決められる。range(0, 5).map(i -> i * 2) は「回数」を range で決めて値を計算する形
9 同じ文字列をくり返すだけなら "ab".repeat(3) が最短
10 2重ループの結果を List にしたいときは、外側を boxed してから flatMap で内側をつなぐ
12 reduce に「大きいほうの位置を残す」関数を渡す。戻り値は OptionalInt なので getAsInt で取り出す

ラムダ式の中から外の変数を書き換えようとしたとき

for文で total += i と書いていた処理を、そのまま forEach に移すとコンパイルエラーになります。

import java.util.stream.*;

public class Capture {
    public static void main(String[] args) {
        int total = 0;
        IntStream.rangeClosed(1, 10).forEach(i -> total += i);
        System.out.println(total);
    }
}
Capture.java:6: error: local variables referenced from a lambda expression must be final or effectively final
        IntStream.rangeClosed(1, 10).forEach(i -> total += i);
                                                  ^
1 error

ラムダ式から参照できるローカル変数は、値が変わらないもの(final か、実質的に final)に限られます。合計は sum()、件数は count()、List 作りは mapToObj と toList() のように、結果を返すメソッドに置き換えるのが IntStream らしい書き方です。この例は int total = IntStream.rangeClosed(1, 10).sum(); の1行になり、結果は 55 です。

boxedとmapToObj:IntStreamをListにする

IntStream は int をそのまま扱うため、List<Integer> のようなコレクションに集める前に、Integer などのオブジェクトに変える必要があります。

メソッド 何に変わるか 例 結果
boxed() int を Integer に包んだストリーム IntStream.range(0, 3).boxed().toList() [0, 1, 2]
mapToObj(関数) 関数が返す型のストリーム IntStream.range(0, 3).mapToObj(i -> "No." + i).toList() [No.0, No.1, No.2]
map(関数) IntStream のまま IntStream.range(0, 3).map(i -> i * 10).toArray() [0, 10, 20]

Integer のまま集めたいなら boxed、別の型に変えたいなら mapToObj と覚えておくと迷いません。

boxedを忘れたときのコンパイルエラーと直し方

IntStream のまま collect(Collectors.toList()) を呼ぶと、次のエラーになります。

import java.util.*;
import java.util.stream.*;

public class BoxedErr {
    public static void main(String[] args) {
        List<Integer> list = IntStream.range(0, 5).collect(Collectors.toList());
        System.out.println(list);
    }
}

javac BoxedErr.java の結果(OpenJDK 21 と 25 の両方で、同じ内容の2つのエラーが出ました):

BoxedErr.java:6: error: method collect in interface IntStream cannot be applied to given types;
        List<Integer> list = IntStream.range(0, 5).collect(Collectors.toList());
                                                  ^
  required: Supplier<R>,ObjIntConsumer<R>,BiConsumer<R,R>
  found:    Collector<Object,CAP#1,List<Object>>
  reason: cannot infer type-variable(s) R
    (actual and formal argument lists differ in length)
  where R is a type-variable:
    R extends Object declared in method <R>collect(Supplier<R>,ObjIntConsumer<R>,BiConsumer<R,R>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
BoxedErr.java:6: error: incompatible types: cannot infer type-variable(s) R
        List<Integer> list = IntStream.range(0, 5).collect(Collectors.toList());
                                                          ^
    (argument mismatch; no instance(s) of type variable(s) CAP#1,T exist so that Collector<T,?,List<T>> conforms to Supplier<R>)
  where R,T are type-variables:
    R extends Object declared in method <R>collect(Supplier<R>,ObjIntConsumer<R>,BiConsumer<R,R>)
    T extends Object declared in method <T>toList()
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
2 errors

長いメッセージですが、読むところは次の2行です。

  • required: Supplier<R>,ObjIntConsumer<R>,BiConsumer<R,R> … IntStream の collect は引数を3つ取る形だけがある
  • found: Collector<...> … そこへ Collector を1つだけ渡している(actual and formal argument lists differ in length=引数の数が合わない)

つまり「IntStream には、Collector を1つ受け取る collect がない」という意味です。関連して、次の2つもコンパイルエラーになります。

import java.util.*;
import java.util.stream.*;

public class BoxedErr2 {
    public static void main(String[] args) {
        List<Integer> list = IntStream.range(0, 5).toList();
        List<String> labels = IntStream.range(0, 5).map(i -> "No." + i).boxed().toList();
    }
}
BoxedErr2.java:6: error: cannot find symbol
        List<Integer> list = IntStream.range(0, 5).toList();
                                                  ^
  symbol:   method toList()
  location: interface IntStream
BoxedErr2.java:7: error: incompatible types: bad return type in lambda expression
        List<String> labels = IntStream.range(0, 5).map(i -> "No." + i).boxed().toList();
                                                                   ^
    String cannot be converted to int
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
2 errors
エラー文 原因 直し方
method collect in interface IntStream cannot be applied to given types IntStream のまま Collector を渡した collect の前に boxed() を入れる
cannot find symbol ... method toList() ... interface IntStream IntStream には toList() がない boxed().toList() にする
String cannot be converted to int map は int を返す関数だけを受け取る 文字列などに変えるなら mapToObj を使う

直したコードと実行結果です。

import java.util.*;
import java.util.stream.*;

public class BoxedFix {
    public static void main(String[] args) {
        // boxed() で IntStream を Integer のストリームに変える
        List<Integer> a = IntStream.range(0, 5).boxed().collect(Collectors.toList());
        List<Integer> b = IntStream.range(0, 5).boxed().toList();   // Java 16以降
        // int 以外に変えるなら mapToObj
        List<String> c = IntStream.range(0, 5).mapToObj(i -> "No." + i).toList();
        // 配列でよければ boxed は不要
        int[] d = IntStream.range(0, 5).toArray();
        // Set や Map にも集められる
        Set<Integer> e = IntStream.range(0, 5).boxed().collect(Collectors.toSet());
        Map<Integer, String> f = IntStream.range(0, 3).boxed().collect(Collectors.toMap(i -> i, i -> "No." + i));

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(Arrays.toString(d));
        System.out.println(e);
        System.out.println(f);
    }
}
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
[No.0, No.1, No.2, No.3, No.4]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
{0=No.0, 1=No.1, 2=No.2}

int の配列で足りる場面では、boxed を使わず toArray() で受け取るのが最も手軽です。

sumとsummaryStatistics:集計のまとめ

IntStream には、集計用のメソッドが最初から用意されています(一覧は冒頭の早見表「集計する」のとおり)。

max・min・average は、空のストリームでは値がないため Optional で返ります。値を取り出すときは getAsInt() や orElse(0) を使います。

件数・合計・最小・平均・最大を一度に知りたいときは summaryStatistics が便利です。1回の走査で5つの値がそろいます。早見表の実行結果から抜き出すと次のとおりです。

summaryStatistics  : IntSummaryStatistics{count=4, sum=950, min=80, average=237.500000, max=450}
  個別に取り出す   : 4 / 950 / 80 / 450 / 237.5

個別の値は getCount()・getSum()・getMin()・getMax()・getAverage() で取り出します。

合計が int の範囲を超えるときは long にする

sum() の戻り値は int です。合計が int の上限(2147483647)を超えると、エラーにならないまま値があふれます。

asLongStream.sum   : 5000050000
intのままsum       : 705082704

1 から 10万までの合計は 5000050000 ですが、int のまま sum すると 705082704 になりました。大きな合計になりそうなら asLongStream() や mapToLong(...) で LongStream にしてから sum します。

同じストリームは1回しか使えない

IntStream を変数に入れて2回使うと、2回目で例外になります。

import java.util.stream.*;

public class Reuse {
    public static void main(String[] args) {
        IntStream s = IntStream.range(0, 3);
        System.out.println(s.sum());
        System.out.println(s.max());
    }
}

実行結果(3行目以降のスタックトレースは省略):

3
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

1回目の sum は 3 を返し、2回目の max で IllegalStateException になりました。ストリームは終端操作を1回実行すると使い終わりです。複数の集計が必要なら summaryStatistics でまとめて取るか、IntStream.range(0, 3) をその都度作ります。

for文との実行時間を簡易計測した結果

「IntStream にすると速さは変わるのか」を確かめるため、同じ処理を for文と IntStream で書き、実行時間を比べました。各処理を5回ウォームアップしたあと7回実行し、いちばん短かった時間を載せています。

import java.util.function.*;
import java.util.stream.*;

public class Bench {
    static final int N = 100_000_000;
    static final int[] DATA = IntStream.range(0, 10_000_000).toArray();

    static long forSum() { long s = 0; for (int i = 0; i < N; i++) { s += i; } return s; }
    static long streamSum() { return IntStream.range(0, N).asLongStream().sum(); }
    static long forEven() { long s = 0; for (int i = 0; i < N; i++) { if (i % 2 == 0) { s += (long) i * 3; } } return s; }
    static long streamEven() { return IntStream.range(0, N).filter(i -> i % 2 == 0).mapToLong(i -> (long) i * 3).sum(); }
    static long forArray() { long s = 0; for (int i = 0; i < DATA.length; i++) { s += DATA[i]; } return s; }
    static long streamArray() { return IntStream.range(0, DATA.length).mapToLong(i -> DATA[i]).sum(); }
    static long boxedSum() { return IntStream.range(0, 10_000_000).boxed().mapToLong(Integer::longValue).sum(); }
    static long forSmall() { long s = 0; for (int i = 0; i < 10_000_000; i++) { s += i; } return s; }

    static void measure(String label, LongSupplier f) {
        for (int i = 0; i < 5; i++) { f.getAsLong(); }            // ウォームアップ
        long best = Long.MAX_VALUE, result = 0;
        for (int i = 0; i < 7; i++) {
            long t = System.nanoTime();
            result = f.getAsLong();
            best = Math.min(best, System.nanoTime() - t);
        }
        System.out.printf("%-34s %8.1f ms  (結果 %d)%n", label, best / 1_000_000.0, result);
    }

    public static void main(String[] args) {
        System.out.println("Java " + Runtime.version() + " / CPU " + Runtime.getRuntime().availableProcessors());
        measure("合計 1億回 for", Bench::forSum);
        measure("合計 1億回 IntStream", Bench::streamSum);
        measure("偶数だけ3倍して合計 1億回 for", Bench::forEven);
        measure("偶数だけ3倍して合計 1億回 IntStream", Bench::streamEven);
        measure("配列1000万件の合計 for", Bench::forArray);
        measure("配列1000万件の合計 IntStream", Bench::streamArray);
        measure("合計 1000万回 for", Bench::forSmall);
        measure("合計 1000万回 boxedあり", Bench::boxedSum);
    }
}

OpenJDK 25.0.4.1 での実行結果:

Java 25.0.4.1+1-1-24.04.4-Ubuntu / CPU 2
合計 1億回 for                             57.3 ms  (結果 4999999950000000)
合計 1億回 IntStream                       75.2 ms  (結果 4999999950000000)
偶数だけ3倍して合計 1億回 for                     63.1 ms  (結果 7499999850000000)
偶数だけ3倍して合計 1億回 IntStream              580.1 ms  (結果 7499999850000000)
配列1000万件の合計 for                         1.3 ms  (結果 49999995000000)
配列1000万件の合計 IntStream                  78.6 ms  (結果 49999995000000)
合計 1000万回 for                           7.9 ms  (結果 49999995000000)
合計 1000万回 boxedあり                     103.7 ms  (結果 49999995000000)

OpenJDK 21.0.10 でも同じコードを実行しました。1億回の合計は for が 74.1 ms、IntStream が 69.0 ms、偶数だけ3倍して合計は for が 74.8 ms、IntStream が 374.6 ms で、大小関係の傾向は同じでした。

処理 Java 25:for Java 25:IntStream 読み取れること
1億回の合計 57.3 ms 75.2 ms 単純な合計は近い時間(Java 21 では IntStream のほうが短かった)
偶数だけ3倍して合計(1億回) 63.1 ms 580.1 ms filter と変換を重ねた処理は for のほうが短かった
配列1000万件の合計 1.3 ms 78.6 ms インデックスで配列を読む処理は for のほうが大幅に短かった
1000万回の合計(boxed あり) 7.9 ms 103.7 ms boxed で Integer に包むと、その分の時間がかかる

この数値は、CPU 2コアの共有マシンで、ほかの処理も動いている状態での簡易計測です。実行のたびに数値は揺れ、Java 21 と 25 の差もこの計測からは判断できません。マシンや処理内容によって変わるので、大小関係の目安として読んでください。読み取れる傾向は次の3点です。

  • 単純な合計は、1億回でも for文と IntStream が近い時間だった
  • filter と変換を重ねた億単位のくり返しや、配列をインデックスで読む計算は for文が短時間で済んだ
  • boxed は必要なところだけ。int のまま計算できる処理(sum・map・filter・toArray)は IntStream のまま進める

for文とIntStreamのどちらを選ぶかの判断表

状況 向いている書き方 理由
連番から List や配列を作る IntStream mapToObj(...).toList() や toArray() の1行で書ける
合計・最大・平均などの集計 IntStream sum・max・summaryStatistics が用意されている
条件で絞って変換する流れ IntStream filter → map と、処理の順に読める
ループの途中で break したい for文 forEach のラムダ式の中に break は書けない(下のエラー文)
外の変数を書き換えながら進める for文 ラムダ式からはローカル変数を書き換えられない
億単位の回数や、配列を読む数値計算 for文 上の計測では for文のほうが短時間だった
要素を順に処理するだけ(インデックス不要) 拡張for文 / list.forEach IntStream を経由しないほうが短い

forEach のラムダ式の中に if (i == 3) { break; } と書いてコンパイルすると、次のエラーになりました。

Brk.java:6: error: break outside switch or loop
            if (i == 3) { break; }
                          ^
1 error

「条件に合う最初の1件で止めたい」場合は、break の代わりに anyMatch(...) や takeWhile(...) が使えます。早見表の実行結果のとおり、IntStream.range(0, 10).takeWhile(i -> i < 4) は 0, 1, 2, 3 で止まります。

よくある質問

Q. range と rangeClosed はどちらを使えばよいですか。 A. 配列や List のインデックスのように「0 から size の手前まで」なら range、「1 から 12 まで」のように終わりの値も含めたいなら rangeClosed です。for文の < が range、<= が rangeClosed に対応します。

Q. IntStream を List に変えるにはどうしますか。 A. IntStream.range(0, 5).boxed().toList() のように、boxed() で Integer のストリームにしてから集めます(Java 16 より前は boxed().collect(Collectors.toList()))。文字列など別の型の List にしたいときは mapToObj(i -> ...) を使います。

Q. 逆順や2刻みでくり返すにはどう書きますか。 A. 3引数の iterate が for文に近く書けます。逆順は IntStream.iterate(4, i -> i >= 0, i -> i - 1)、2刻みは IntStream.iterate(0, i -> i < 10, i -> i + 2) です。range と map を組み合わせた IntStream.range(0, 5).map(i -> 4 - i) でも同じ結果になります。

Q. IntStream と Stream(Integer のストリーム)は何が違いますか。 A. IntStream は int をそのまま扱い、sum・average・summaryStatistics などの数値向けメソッドを持ちます。Integer のストリームはオブジェクトを扱うので、collect で List や Map に集められます。boxed() で IntStream から Integer のストリームへ、mapToInt(...) でその逆へ変換できます。

まとめ

  • range は終わりを含まず、rangeClosed は含む。for文の < と <= に対応する
  • 逆順・2刻みは 3引数の iterate(初期値・続ける条件・次の値)で for文と同じ感覚で書ける
  • List に集める前に boxed()、別の型にするなら mapToObj。忘れると collect の引数が合わないというコンパイルエラーになる
  • 集計は sum・max・average、まとめて知りたいなら summaryStatistics。合計が大きくなるなら asLongStream で long にする
  • ラムダ式からは外のローカル変数を書き換えられないので、結果を返すメソッド(sum、toList など)に置き換える
  • ストリームは1回使うと終わり。もう一度使うときは作り直す
  • 連番からの List 生成や集計は IntStream、break が必要な処理や億単位の数値計算は for文と使い分ける

参考・出典

  • Oracle「Java SE 21 API仕様 IntStream」https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/IntStream.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java IntStreamの使い方|range・boxed・mapToObj」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?