Java Tips | コレクション:Stream生成

Java Java
スポンサーリンク

Stream生成は「データに“流れ”をつける」入り口

Stream は「コレクションや配列などの要素を、流れとして扱うためのビュー」です。
filtermapcollect などの“Stream 操作”を使うためには、まず「Stream をどうやって作るか(生成するか)」を押さえる必要があります。

業務コードでは、ほぼ次のパターンから Stream を作ります。
コレクション(List・Set)から
配列から
単発の値や可変長引数から
数値レンジや乱数などの“連番・無限列”から

ここを整理しておくと、「とりあえず Stream にしてから考える」という書き方ができるようになります。


コレクションからの Stream生成

List・Set からは stream() が基本

Collection(List・Set など)には、stream() メソッドが用意されています。

import java.util.List;
import java.util.stream.Stream;

public class FromCollection {

    public static void main(String[] args) {
        List<String> names = List.of("山田", "佐藤", "鈴木");

        Stream<String> stream = names.stream();

        stream
            .filter(n -> n.startsWith("佐"))
            .forEach(System.out::println); // 佐藤
    }
}
Java

ここでの重要ポイントは、stream() が「元データをコピーするのではなく、“ビュー”を作るだけ」ということです。
Stream 自体は“使い捨て”で、一度終端操作(forEachcollect など)を呼ぶと再利用できません。

「List で持っておく → 必要なときに list.stream() で流れを作る」というスタイルを基本形として覚えてください。


配列からの Stream生成

参照型配列:Arrays.stream と Stream.of

String[] などの参照型配列から Stream を作る代表的な方法は二つあります。

import java.util.Arrays;
import java.util.stream.Stream;

public class FromArrayRef {

    public static void main(String[] args) {
        String[] array = {"A", "B", "C"};

        Stream<String> s1 = Arrays.stream(array);
        Stream<String> s2 = Stream.of(array);

        s1.forEach(System.out::println); // A B C
    }
}
Java

参照型配列に関しては、Arrays.streamStream.of も「要素ごとの Stream」を返すので、挙動はほぼ同じです。

ただし、「“配列そのもの”を 1 要素として扱いたい」場合は Stream.of(array)(型は Stream<String[]>)という使い方もあるため、
「配列の中身を流したいのか」「配列自体を流したいのか」を意識して選ぶのが大事です。

プリミティブ配列:Arrays.stream 一択で覚える

int[] などのプリミティブ配列は、Arrays.stream を使うのが基本です。

import java.util.Arrays;
import java.util.stream.IntStream;

public class FromArrayPrimitive {

    public static void main(String[] args) {
        int[] nums = {1, 2, 3};

        IntStream stream = Arrays.stream(nums);

        int sum = stream.sum();
        System.out.println(sum); // 6
    }
}
Java

ここでの重要ポイントは二つです。
Arrays.stream(int[])IntStream を返すので、sumaverage など“数値専用の便利メソッド”が使える。
Stream.of(nums) だと Stream<int[]> になってしまい、「配列1個が1要素」になってしまうので、配列の中身を処理したいときには不向き。

「配列の要素を処理したいなら、プリミティブでも参照型でも、とりあえず Arrays.stream」と覚えておくと安全です。


単発値・可変長引数からの Stream生成

Stream.of で「その場でちょっとした Stream」を作る

「わざわざ List を作るほどでもないけれど、Stream で書きたい」というときは、Stream.of が便利です。

import java.util.stream.Stream;

public class FromValues {

    public static void main(String[] args) {
        Stream<String> stream =
                Stream.of("山田", "佐藤", "鈴木");

        long count =
                stream.filter(n -> n.length() == 2)
                      .count();

        System.out.println(count);
    }
}
Java

ここでの重要ポイントは、Stream.of が「引数に渡した値を、そのまま順番付きの Stream にしてくれる」ことです。
テストコードや、ちょっとした分岐処理などで「3〜4個の候補を Stream で処理したい」ときに、とても書きやすくなります。


数値レンジ・無限列からの Stream生成

IntStream.range / rangeClosed で「連番の Stream」

ループの代わりに「1〜N までの連番」を Stream で扱いたいときは、IntStream.range 系を使います。

import java.util.stream.IntStream;

public class FromRange {

    public static void main(String[] args) {
        IntStream.range(0, 5)          // 0,1,2,3,4
                 .forEach(System.out::println);

        IntStream.rangeClosed(1, 3)    // 1,2,3
                 .forEach(System.out::println);
    }
}
Java

range(開始, 終了) は「開始以上、終了未満」、
rangeClosed(開始, 終了) は「開始以上、終了以下」です。

for ループの代わりに「インデックスの Stream」を作るイメージで使うと、
mapToObj(i -> ...) などと組み合わせて柔軟な処理が書けるようになります。

Stream.generate / iterate で「無限列」

「同じ値を繰り返す」「前の値から次の値を計算する」といった“無限列”も Stream で作れます。

import java.util.stream.Stream;

public class FromInfinite {

    public static void main(String[] args) {
        Stream.generate(() -> "A")
              .limit(3)
              .forEach(System.out::println); // A A A

        Stream.iterate(1, n -> n + 1)
              .limit(5)
              .forEach(System.out::println); // 1 2 3 4 5
    }
}
Java

ここでの重要ポイントは、「必ず limit などで打ち切ること」です。
無限列は強力ですが、終端操作までに“どこかで止める”ことを忘れると、延々と処理が続いてしまいます。


Stream生成ユーティリティで「入口の迷い」を減らす

「とりあえず Stream にする」ための小さなヘルパー

実務では、「null かもしれないコレクション」「配列かもしれないし単体かもしれない値」など、
“そもそも何から Stream を作るか”で毎回 if 文を書きがちです。

そういうときは、入口だけユーティリティに寄せてしまうとスッキリします。

import java.util.Collection;
import java.util.stream.Stream;

public final class Streams {

    private Streams() {}

    public static <T> Stream<T> from(Collection<T> c) {
        if (c == null || c.isEmpty()) {
            return Stream.empty();
        }
        return c.stream();
    }

    @SafeVarargs
    public static <T> Stream<T> ofNullable(T... values) {
        if (values == null || values.length == 0) {
            return Stream.empty();
        }
        return Stream.of(values);
    }
}
Java

使い方のイメージです。

Streams.from(list)
       .filter(...)
       .forEach(...);

Streams.ofNullable(maybeValue)
       .filter(...)
       .forEach(...);
Java

「null なら空 Stream」「そうでなければ普通の Stream」という入口を一箇所に閉じ込めることで、
呼び出し側は「とにかく Stream として扱う」ことだけに集中できます。


まとめ:Stream生成で身につけてほしい感覚

Stream生成は、
単に「書き方を暗記する」話ではなく、
「データの“持ち方”と“流し方”を分けて考えるための入口設計」です。

コレクションなら collection.stream()
参照型配列なら Arrays.stream(array)
プリミティブ配列なら Arrays.stream(primitiveArray)
単発値なら Stream.of(...)
連番なら IntStream.range / rangeClosed
無限列なら Stream.generate / iterate

このあたりを“手癖”で選べるようになると、
「まず Stream にしてから、filter / map / collect を考える」という書き方が自然にできるようになります。

あなたのコードのどこかに、
同じような for ループが何度も出てきているなら、
一度「その入口を Stream生成に置き換えられないか」を眺めてみてください。

その小さな一歩が、
「データ処理を“流れ”として設計できるエンジニア」への、
確かなステップになります。

タイトルとURLをコピーしました