🌪️

JavaのStreamAPIについてまとめてみた

に公開

はじめに

n番煎じかわかりませんが、Java8以降を使う上で高頻度で使うなぁと思い、自分用という意味も込めてStreamAPIについてまとめました。ラムダ式やStreamAPIについての基本的な概要は先人の方が詳しく紹介していらっしゃると思いますのでそれぞれの基本操作などを書き留めていきます!

filter() - 条件に合う要素だけを抽出

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 偶数だけを抽出
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());

// 結果: [2, 4, 6, 8, 10]

map() - 各要素を変換

List<String> words = Arrays.asList("apple", "banana", "cherry");

// 全て大文字に変換
List<String> upperWords = words.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// 結果: [APPLE, BANANA, CHERRY]

// 文字列の長さに変換
List<Integer> lengths = words.stream()
    .map(String::length)
    .collect(Collectors.toList());

// 結果: [5, 6, 6]

collect() - 結果を収集

List<String> fruits = Arrays.asList("apple", "banana", "cherry", "apple");

// Listとして収集
List<String> list = fruits.stream()
    .filter(f -> f.length() > 5)
    .collect(Collectors.toList());
// 結果: [banana, cherry]

// Setとして収集(重複除去)
Set<String> set = fruits.stream()
    .collect(Collectors.toSet());
// 結果: [apple, banana, cherry]

// 文字列として結合
String joined = fruits.stream()
    .collect(Collectors.joining(", "));
// 結果: "apple, banana, cherry, apple"

peek() - 中間操作中の値を出力

numbers.stream()
    .peek(n -> {
        if (n % 2 == 0) {
            System.out.println("偶数を処理中: " + n);
        } else {
            System.out.println("奇数を処理中: " + n);
        }
    })
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect(Collectors.toList());

その他の便利な基本操作

List<Integer> scores = Arrays.asList(85, 92, 78, 95, 88, 91, 76, 89);

// 任意の要素が条件を満たすかチェック
boolean hasExcellent = scores.stream()
    .anyMatch(score -> score >= 95);

// 全ての要素が条件を満たすかチェック
boolean allPassed = scores.stream()
    .allMatch(score -> score >= 60);

// 最初に見つかった要素を取得
Optional<Integer> firstHigh = scores.stream()
    .filter(score -> score >= 90)
    .findFirst();

// 要素数をカウント
long highScoreCount = scores.stream()
    .filter(score -> score >= 90)
    .count();

// 最大値・最小値・平均値
OptionalInt max = scores.stream().mapToInt(Integer::intValue).max();
OptionalInt min = scores.stream().mapToInt(Integer::intValue).min();
OptionalDouble average = scores.stream().mapToInt(Integer::intValue).average();

flatMap()の実用例

flatMap()とは?

flatMap()は「平坦化」を行う操作で、ネストした構造を一次元に変換します。意外と使いどころが多い便利な機能です。

実用例1: ネストしたリストの処理

List<List<String>> nestedList = Arrays.asList(
    Arrays.asList("Java", "Python"),
    Arrays.asList("JavaScript", "TypeScript", "Go"),
    Arrays.asList("C++", "Rust")
);

List<String> flatLanguages = nestedList.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());

// 結果: [Java, Python, JavaScript, TypeScript, Go, C++, Rust]

実用例2: 文字列を文字に分解

List<String> sentences = Arrays.asList("Hello World", "Java Stream");

List<Character> allCharacters = sentences.stream()
    .flatMap(sentence -> sentence.chars().mapToObj(c -> (char) c))
    .filter(c -> c != ' ')  // スペースを除外
    .collect(Collectors.toList());

// 結果: [H, e, l, l, o, W, o, r, l, d, J, a, v, a, S, t, r, e, a, m]

実用例3: オブジェクトから複数の値を抽出

List<Person> people = Arrays.asList(
    new Person("Alice", Arrays.asList("Java", "Python", "JavaScript")),
    new Person("Bob", Arrays.asList("C++", "Go")),
    new Person("Charlie", Arrays.asList("Rust", "TypeScript", "Java"))
);

// 全員のスキルを一つのリストに
List<String> allSkills = people.stream()
    .flatMap(person -> person.getSkills().stream())
    .distinct()
    .sorted()
    .collect(Collectors.toList());

flatMap()の応用パターン

ファイル処理での活用

List<String> filePaths = Arrays.asList(
    "documents/report.pdf,backup.txt",
    "images/photo1.jpg,photo2.png",
    "code/Main.java,Utils.class"
);

List<String> extensions = filePaths.stream()
    .flatMap(path -> Arrays.stream(path.split(",")))
    .map(file -> file.substring(file.lastIndexOf(".") + 1))
    .distinct()
    .collect(Collectors.toList());

// 結果: [pdf, txt, jpg, png, java, class]

Optionalとの組み合わせ

実用例1: nullに対して安全な処理

List<String> mixedData = Arrays.asList("apple", null, "banana", "", "cherry", null);

List<String> safeProcessed = mixedData.stream()
    .map(Optional::ofNullable)
    .flatMap(Optional::stream)
    .filter(s -> !s.isEmpty())
    .map(String::toUpperCase)
    .collect(Collectors.toList());

// 結果: [APPLE, BANANA, CHERRY]

実用例2: 条件に合う要素の安全な取得

List<Integer> scores = Arrays.asList(85, 92, 78, 95, 88, 91);

Optional<Integer> firstExcellent = scores.stream()
    .filter(score -> score >= 90)
    .findFirst();

firstExcellent.ifPresentOrElse(
    score -> System.out.println("最初の優秀な点数: " + score),
    () -> System.out.println("90点以上の点数はありませんでした")
);

実用例3: デフォルト値の活用

Integer firstExcellentOrDefault = scores.stream()
    .filter(score -> score >= 95)
    .findFirst()
    .orElse(0);  // 見つからなければ0を返す

高度なOptional + Stream活用法

Optionalのリストを処理

List<Optional<String>> optionalList = Arrays.asList(
    Optional.of("apple"),
    Optional.empty(),
    Optional.of("banana"),
    Optional.empty(),
    Optional.of("cherry")
);

List<String> presentValues = optionalList.stream()
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

// 結果: [apple, banana, cherry]

flatMapでOptionalを平坦化

List<String> flatMappedValues = optionalList.stream()
    .flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty))
    .collect(Collectors.toList());

最後に

見ていただいてありがとうございました!
最近Java使ってないなぁと思いつつ、けどやっぱり一番使ってる言語はJavaなので頭の引き出し整理も兼ねて初めて記事にしてみました。また何かあれば記事書いてみようかなと思います✏️

マーベリックスのテックブログ

Discussion