「Stream の生成」をざっくりイメージする
Stream API の最初の一歩は、「そもそも Stream をどうやって作るか」です。filter や map は、その前に「Stream がある」ことが前提になります。
イメージとしては、
「元データ(List・配列・ファイルなど)から、“データの流れ”を取り出す」
という作業が「Stream の生成」です。
よく使うパターンから順に押さえていきましょう。
最初は「List から」「配列から」あたりを固めれば十分です。
コレクション(List / Set / Map)からの Stream 生成
List / Set から stream() を呼ぶ
いちばんよく使うのがこれです。
List や Set は java.util.Collection を実装していて、stream() メソッドを持っています。
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class FromList {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Carol");
Stream<String> stream = names.stream();
stream
.filter(name -> name.length() <= 4)
.forEach(System.out::println);
}
}
Javanames.stream() の時点で、Stream<String> が生成されています。
そこに対して filter や forEach をつなげている、という形です。
ここで大事なのは、「List に対して直接 for 文を回さず、“まず stream() で流れに変換する”」という感覚です。
補足として、Set<String> からも全く同じように set.stream() で生成できます。
Set<String> set = new HashSet<>();
// 何か追加して…
Stream<String> setStream = set.stream();
JavaMap からの Stream(entrySet / keySet / values を使う)
Map は、そのままでは stream() を持っていません。
キーだけ・値だけ・キーと値のペア、のどれをストリームにするかを自分で決めます。
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class FromMap {
public static void main(String[] args) {
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 80);
scores.put("Bob", 65);
scores.put("Carol", 90);
Stream<Map.Entry<String, Integer>> entryStream =
scores.entrySet().stream();
entryStream
.filter(e -> e.getValue() >= 70)
.forEach(e -> System.out.println(e.getKey() + " : " + e.getValue()));
}
}
Javaキーのストリームにしたければ scores.keySet().stream()、
値だけなら scores.values().stream() という流れです。
「Map から Stream を作るときは、まず Set や Collection に変換してから」というパターンを覚えておくとスムーズです。
配列からの Stream 生成(Arrays.stream / Stream.of)
Arrays.stream(配列) を使う
配列から Stream を作る代表的な方法が Arrays.stream です。
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class FromArray {
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Carol"};
Stream<String> stream = Arrays.stream(names);
stream.forEach(System.out::println);
int[] nums = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(nums);
int sum = intStream.sum();
System.out.println(sum); // 15
}
}
Java参照型(String[] など)の場合は Stream<T>
プリミティブ型(int[] など)の場合は IntStream などの専用ストリームになります。
ここでのポイントは、「配列 → Arrays.stream → Stream」というルートを体に入れておくことです。
List に変換してから list.stream() とするより、Arrays.stream のほうがシンプルで無駄も少ないです。
Stream.of(…) で“直接”作る
少数の値から簡単に Stream を作りたいときは Stream.of もよく使います。
import java.util.stream.Stream;
public class FromOf {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Alice", "Bob", "Carol");
stream
.map(String::toUpperCase)
.forEach(System.out::println);
}
}
Javaテストコードやサンプルコードなど、「ちょっとだけ値を流したい」という場面で便利です。Stream.of(array) のように書くと、一見配列を 1 要素として流してしまうので、
「配列から要素を流したいときは Arrays.stream を使う」と覚えておく方が安全です。
数値用のストリーム(IntStream / LongStream / DoubleStream)
mapToInt や Arrays.stream(int[]) で生成する
Stream<Integer> よりも、IntStream などのプリミティブ専用ストリームのほうが、sum や average などの数値系操作がしやすく、性能的にも有利なことが多いです。
配列からの例はすでに出しましたが、mapToInt から生成するパターンもよく使います。
import java.util.Arrays;
public class IntStreamExample {
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Carol"};
int totalLength =
Arrays.stream(names) // Stream<String>
.mapToInt(String::length) // IntStream に変換
.sum();
System.out.println(totalLength); // 13
}
}
Javaここでは、
Stream<String> の mapToInt で IntStream を生成IntStream の sum() で合計
という流れです。
頭の整理としては、
参照型のストリームから mapToInt / mapToLong / mapToDouble で数値ストリームを生成
あるいは、最初から Arrays.stream(int[]) などで数値ストリームを作る
と押さえておけば十分です。
ファイルや文字列など「外部データ」からの Stream 生成
Files.lines(Path) で“1 行ずつの Stream”を作る
ファイルを 1 行ずつ Stream として扱いたいときは Files.lines が定番です。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class FromFile {
public static void main(String[] args) throws IOException {
Path path = Path.of("sample.txt");
try (Stream<String> lines = Files.lines(path)) {
long count =
lines
.filter(line -> !line.isBlank())
.count();
System.out.println("空行以外の行数: " + count);
}
}
}
JavaFiles.lines はファイルの中身を全部読み込むのではなく、「行を順に読みながら流していく」イメージです。try-with-resources を使って、使い終わったら自動的にクローズされるように書くのが基本形です。
Pattern.splitAsStream で“区切り文字ごとの Stream”を作る
文字列を正規表現で区切って Stream にしたいときは、Pattern と splitAsStream を使う方法もあります。
import java.util.regex.Pattern;
public class FromStringSplit {
public static void main(String[] args) {
String text = "apple,banana,orange";
Pattern.compile(",")
.splitAsStream(text)
.forEach(System.out::println);
}
}
Java「カンマ区切り」「スペース区切り」など、文字列の前処理でよく使います。
自分で Stream を「生み出す」パターン(generate / iterate / builder)
ここは少し応用寄りですが、「Stream の正体」に近づくところなので、イメージだけ掴んでおくと後で効きます。
Stream.generate で「無限ストリーム」を作る
Stream.generate は、「指定した Supplier を何度も呼び続けるストリーム」を生成します。
import java.util.UUID;
import java.util.stream.Stream;
public class GenerateExample {
public static void main(String[] args) {
Stream<String> randomIds =
Stream.generate(() -> UUID.randomUUID().toString());
randomIds
.limit(3) // 無限に出続けるので、どこかで制限する
.forEach(System.out::println);
}
}
Javagenerate で作ったストリームは「終わりがない」ので、limit などでサイズを制限するのが必須です。
Stream.iterate で「次々と値を計算し続ける」ストリーム
iterate は「前の値から次の値を計算する」タイプのストリームです。
import java.util.stream.Stream;
public class IterateExample {
public static void main(String[] args) {
Stream<Integer> naturalNumbers =
Stream.iterate(1, n -> n + 1);
naturalNumbers
.limit(5)
.forEach(System.out::println); // 1, 2, 3, 4, 5
}
}
Javaiterate(初期値, 次の値を計算する関数) という形で使います。
これも無限ストリームなので、limit などが必須です。
Stream.builder で「後から要素を追加していく」
要素を一つずつ積み上げて、最後に Stream として使いたい場合は Stream.builder もあります。
import java.util.stream.Stream;
public class BuilderExample {
public static void main(String[] args) {
Stream<String> stream =
Stream.<String>builder()
.add("Alice")
.add("Bob")
.add("Carol")
.build();
stream.forEach(System.out::println);
}
}
Javaテストデータを柔軟に組み立てるときなどに便利ですが、
初心者の段階では「こういうのもあるんだ」くらいで構いません。
空の Stream と Optional からの Stream
Stream.empty() で「空のストリーム」
要素ゼロのストリームが欲しいときは Stream.empty() を使えます。
import java.util.stream.Stream;
public class EmptyExample {
public static void main(String[] args) {
Stream<String> empty = Stream.empty();
long count = empty.count(); // 0
System.out.println(count);
}
}
Javaメソッドの戻り値として「要素がない場合は空の Stream を返す」という設計にすると、
呼び出し側で null チェックをせずに済むので、コードがすっきりします。
Optional からの Stream(少し応用)
Java 9 以降では、Optional に stream() メソッドがあります。
import java.util.Optional;
public class OptionalStreamExample {
public static void main(String[] args) {
Optional<String> maybeName = Optional.of("Alice");
long count =
maybeName.stream()
.filter(name -> name.startsWith("A"))
.count();
System.out.println(count); // 1
}
}
JavaOptional.empty() の場合は「要素ゼロの Stream」として扱われるので、
Stream パイプラインに Optional を自然に混ぜ込むことができます。
まとめ:Stream の生成をどう頭に定着させるか
Stream の生成を初心者向けに整理すると、こうなります。
一番大事なのは、
- List や Set →
collection.stream() - 配列 →
Arrays.stream(array) - 少数の値 →
Stream.of(...)
この 3 つです。これだけでも、日常的なコードの 8 割くらいは賄えます。
そこに加えて、
- Map →
map.entrySet().stream()などにしてから - 数値系 →
IntStreamなど(Arrays.stream(int[])やmapToInt) - ファイル →
Files.lines(path) - 無限・計算系 →
Stream.generate,Stream.iterate - 完全に空 →
Stream.empty()
あたりを、「ああ、そういう入り口があるんだな」というレベルで覚えておくと、
「欲しいときに調べればすぐ書ける」状態になります。
