okinawa

ITと英語の勉強メモがメイン

JavaのStreamAPIメモ

職場でStreamAPIを使ったので備忘録として。

参考

入門Javaのラムダ式とStream API #Java - Qiita

【Java】 Stream(filter, map, forEach, reduce) #java8 - Qiita

読み方

        // Stream APIを使用して条件に合うメンバーを抽出し、該当する場合に「たぬき」を表示
        memberList4.stream()
                .filter(member -> "ぽんぽこ".equals(member.name()))
                .filter(member -> member.age() >= 1000)
                .forEach(member -> {
                    System.out.println("たぬき");
                });

filterがif文であり、条件をクリアしたら下に処理が移っていくと思っておけばOK。

memberList4を分解して、1個ずつ引数のmemberに渡っていくイメージ。

forEachの中にやりたい処理を書く。

filter

if文

        //recordはgetter/setterやコンストラクタを自動生成してくれる便利機能
        // ここではnameとageのgetter/setterが作られている
        record Member(String name, int age) {
        }


        //ネタ元のList生成
        List<Member> memberList4 = List.of(
                new Member("ぽんぽこ", 1006),
                new Member("ピーナッツくん", 5),
                new Member("チャンチョ", 10),
                new Member("コモラ", 16));


        //1000歳以上で名前がぽんぽこの場合 拡張for文バージョン
        for(Member member : memberList4) {
            if(member.name.equals("ぽんぽこ")) {
                if(member.age >= 1000) {
                    //何らかの処理
                }
            }
        }

        // Stream APIバージョン
         memberList4.stream()
                .filter(member -> "ぽんぽこ".equals(member.name()))
                .filter(member -> member.age() >= 1000);

名前がぽんぽこで、年齢が1000歳以上が条件。

ここは見たままなので特に解説はなし。

forEach

繰り返し処理。

filter条件をクリアした場合だけ名前を表示したい。

       //recordはgetter/setterやコンストラクタを自動生成してくれる便利
        // ここではnameとageのgetter/setterが作られている
        record Member(String name, int age) {
        }

        //ネタ元のList生成
        List<Member> memberList4 = List.of(
                new Member("ぽんぽこ", 1006),
                new Member("ピーナッツくん", 5),
                new Member("チャンチョ", 10),
                new Member("コモラ", 16));
        
        //1000歳以上で名前がぽんぽこの場合 for文バージョン
        for(Member member : memberList4) {
            if(member.name.equals("ぽんぽこ")) {
                if(member.age >= 1000) {
                    System.out.println(member.name());
                }
            }
        }

        // Stream APIを使用して条件に合うメンバーを抽出し、該当する場合に「たぬき」を表示
        memberList4.stream()
                .filter(member -> "ぽんぽこ".equals(member.name()))
                .filter(member -> member.age() >= 1000)
                .forEach(member -> System.out.println(member.name()));

forEachが最初ではなくて、最後にforEachが来るのがポイント。

filter条件をクリアしたものだけforEachの処理に到達する。

補足↓ 拡張for文とforEach比較。全ての名前を表示する。

        //拡張for文
        for(Member member : memberList4) {
            System.out.println(member.name());
        }

        //forEach
        memberList4.forEach(member -> System.out.println(member.name()));

map

型の変換やら値の更新をしたいときに使う。

実体としては、関数型インターフェイスR apply(T t)を実装している。

つまりT型を受け取ってR型を返却さえすればよいので、かなり何でもできる。

RとTが同じ型でも良い。

       //recordはgetter/setterやコンストラクタを自動生成してくれる便利
        // ここではnameとageのgetter/setterが作られている
        record Member(String name, int age) {
        }

        //ネタ元のList生成
        List<Member> memberList4 = List.of(
                new Member("ぽんぽこ", 1006),
                new Member("ピーナッツくん", 5),
                new Member("チャンチョ", 10),
                new Member("コモラ", 16));

        //for文バージョン
        for(Member member : memberList4) {
            String ageString = String.valueOf(member.age());//ここがmap
            if(ageString.equals("5")) {
                System.out.println(ageString+"歳");
            }
        }
        
        //Streamバージョン
        memberList4.stream()
        .map(member -> String.valueOf(member.age())) //ageをStringに変換
        .filter(stringAge -> stringAge.equals("5")) //filterの引数にはStringに変換後のageが渡る
        .forEach(stringAge -> System.out.println(stringAge+"歳"));

ここでのmapはMember型を受取り、String型を返している。

関数型インターフェイスR apply(T t)String apply(Member member)として実装していることになる。

List or 配列 からStream生成

List<Member> memberList4 = List.of(
        new Member("ぽんぽこ", 1006),
        new Member("ピーナッツくん", 5),
        new Member("チャンチョ", 10),
        new Member("コモラ", 16));

// ListからStreamを生成
Stream<Member> stream = memberList.stream();

Member[] members = {
        new Member("ぽんぽこ", 1006),
        new Member("ピーナッツくん", 5),
        new Member("チャンチョ", 10),
        new Member("コモラ", 16));
};

// 配列からStreamを生成
Stream<Member> stream = Arrays.stream(members);

toList(StreamからListに変換)

Stream<String> mappedStream = ...;
// Listに変換する
List<String> resultList = mappedStream.toList();

ラムダ式記法(省略形と非省略形)

JavaScriptとほとんど同じ。

//非省略形
Hoge hoge = (str) -> {
  return str.length();
};

//省略形
Hoge hoge = str -> str.length();

実践編:値の詰替え

よくある値の詰替処理をStreamAPIでやってみる。

       //recordはgetter/setterやコンストラクタを自動生成してくれる便利
        // ここではnameとageのgetter/setterが作られている
        record Member(String name, int age) {
        }
        
        record Test(String name, int age) {
        }

        //ネタ元のList生成
        List<Member> memberList4 = List.of(
                new Member("ぽんぽこ", 1006),
                new Member("ピーナッツくん", 5),
                new Member("チャンチョ", 10),
                new Member("コモラ", 16));

        //for文バージョン
        for(Member member : memberList4) {
            if("チャンチョ".equals(member.name())) {
                new Test(member.name(), member.age());
            }
        }
        
        //StreamAPIバージョン
        List<Test> testList = memberList.stream()
        .filter(member -> member.name().equals("チャンチョ"))
        .map(member -> new Test(member.name(), member.age())) //Member型からTest型に詰め替え
        .toList();

条件によって処理を変えたい時は?

例えば20歳以上と16歳以下で処理を分けたい時。

ChatGPTに聞いてみいたらif else文を使ったコードが返ってきた。

filterを使って上手いことできないのだろうか?

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        record Member(String name, int age) {
        }

        // 元となるList
        List<Member> memberList = List.of(
                new Member("ぽんぽこ", 1006),
                new Member("ピーナッツくん", 5),
                new Member("チャンチョ", 10),
                new Member("コモラ", 16));

        // Streamを使用して条件に応じた出力を行う
        memberList.stream()
                .forEach(member -> {
                    if (member.age() >= 20) {
                        System.out.println(member.name() + ":20歳以上");
                    } else if (member.age() <= 16) {
                        System.out.println(member.name() + ":16歳以下");
                    } else {
                        System.out.println(member.name() + ":" + member.age() + "歳");
                    }
                });
    }
}

StreamAPIのメリット

読みやすい。

ネストが深くなりづらいので、直感的に読める。

ただ、forEachの中で大量の処理をすると読みづらくなりそう。