Javaの人気を一新させた機能 Javaコース初級 基礎編Day10「StreamAPIとラムダ式」を改めて学習!

デイトラJava
ハック
ハック

こんにちは!運営者のハックです。

今回は「Javaコース初級 基礎編Day10『StreamAPIとラムダ式』」の学習再挑戦について紹介します。

StreamAPIとラムダ式はJava8から導入された超便利な機能

Stream APIラムダ式はJava8で実装され、Javaの人気を一新させた機能です。
これは当時はかなり画期的な記法で、今までのJavaとJava8以降では別物と言えるぐらいに変わりました。

Stream APIとラムダ式の導入により、より宣言的に、つまり「何をするか」を明確に記述することで、「どうやって行うか」を隠蔽することが可能になりました。

モナ
モナ

例えば、スーパーマーケットで買い物リストにあるものだけを素早く探したいと思っていると仮定します。

Stream APIは、その買い物リスト(データのリスト)を通して流れるように、必要なものだけを簡単に選び取る方法を提供します。

そして、ラムダ式は「赤いものだけ選ぶ」とか「価格が500円以下のものだけ選ぶ」のように、簡単な指示を一行で書いて選び取る方法を指定することができます。

このようにStreamAPIとラムダ式を使用することで、Javaでのプログラミングがずっと簡単になります。

StreamAPIとラムダ式を使わなかったらどうなる?

ねこ奈
ねこ奈

じゃあ、そのStreamAPIとラムダ式を使わないと、どのようなコードになるのにゃ?

モナ
モナ

実際にコードを見てみましょう。

お題
「商品リストから価格が300円以上でカテゴリが『食品』の商品の名前をリストアップしよう」

List<Product> products = Arrays.asList(
    new Product("リンゴ", 200, "食品"),
    new Product("バナナ", 300, "食品"),
    new Product("洗剤", 500, "日用品"),
    new Product("パン", 350, "食品")
);

この「List<Product>」中から条件にあう商品の名前をリストアップするコードを紹介します。

Stream APIとラムダ式を使用しない場合

List<String> filteredProductNames = new ArrayList<>();
for (Product product : products) {
    if (product.getPrice() >= 300 && product.getCategory().equals("食品")) {
        filteredProductNames.add(product.getName());
    }
}
System.out.println(filteredProductNames);
// 出力: [バナナ, パン]
ねこ奈
ねこ奈

え~とぉ…

productsの中を繰り返し処理で検索して、かつ、getPriceで価格を取得して、300円かつ「”食品”」の条件を満たす時に filteredProductNamesに加えて…

なんか括弧ばかりで訳が分からなくなってきたのにゃ…

モナ
モナ

これがより複雑な条件になったらどうでしょう?

もっと括弧が増えて、コードも長くなり訳が分からなくなります。

Stream APIとラムダ式を使用した場合

List<String> filteredProductNames = products.stream()
    .filter(product -> product.getPrice() >= 300 && product.getCategory().equals("食品"))
    .map(Product::getName)
    .collect(Collectors.toList());

System.out.println(filteredProductNames);
// 出力: [バナナ, パン]
ねこ奈
ねこ奈

.filterとか「->」とか中級編で見たコードだけど、これがStream APIとラムダ式かにゃ?

確かにさっきのコードよりはスッキリした気がするのにゃ。

Stream API .filter.map のように、リストなどのストリームに対して連続的な処理を行うメソッド群を指します。

ラムダ式「 ->」 を使って表される無名関数(匿名関数)で、上のコードですと「product -> product.getPrice() >= 300 && product.getCategory().equals(“食品”)」の部分が該当します。

今回の比較から分かるように、Stream APIとラムダ式を使うことで、フィルタリングとマッピングの処理を1行で簡潔に記述でき、コードが読みやすくなります。

モナ
モナ

一行で、とは言っても本当にコードを一行にしてしまうと返って見づらくなるため、実際には例のコードのように .filter.map のようなメソッドごとに改行して見やすくします。

よく使う Stream APIを学習しよう

Stream API でよく使うものを一つづつ、詳しく見ていきましょう。

すべての例で以下の配列「entitylist」を使用します。

List entitylist = Arrays.asList("Player", "Goat", "Cow", "Chicken" , "Mob");

filter

filterは条件を満たした値を抽出するメソッドです。先ほどの例でも使用しましたね!

// 文字列の長さが「3」のものを抽出する。
List<String> filterList =
       entitylist.stream()
                        .filter(entity -> entity.length() == 3)
                        .collect(Collectors.toList());

System.out.println(filterList);
// 実行結果
: [Cow,Mob]

map to XX

map は 値を変換するメソッドです。map , mapToInt, mapToLongなど様々な型のオブジェクトへ変換できます。こちらも先ほどの例でも使用しましたね!

// entityList の中身を全て大文字へ変換します。 
entitylist.stream()
          .map(entity -> entity.toUpperCase())
          .forEach(s -> System.out.println(s + " "));
// 実行結果
: PLAYER
 GOAT
 COW CHICKEN MOB

collect

collectあるコレクションを別のコレクションへ変換する際に便利な集約処理を行うメソッドです。他の例でも度々登場していますね!

// 文字列を大文字に変換し、「,」で連結
System.out.println(
	entitylist.stream()
		.map(String::toUpperCase)
		.collect(Collectors.joining(", "))
);
// 実行結果
:PLAYER, GOAT, COW, CHICKEN, MOB

sort

sort要素をソートするメソッドです。数値の場合は昇順、文字列の場合はアルファベット順になります。

entitylist.stream()
          .sorted()
          .forEach(System.out::println);
// 実行結果:Chiken, Cow, Goat, Mob, Player

foreach

foreachコレクションの要素をイテレート、つまり要素ごとに繰り返し処理を行います。処理内容は forEach() の引数内に指定します。foreachも度々登場しています!

//文字数が5以上の時に、リストから2つまで限定して表示する
entitylist.stream().filter(s -> s.length() > 5)
		.limit(2)
		.forEach(System.out::println);
// 実行結果:Player Chicken
モナ
モナ

他にも「limit」「distinct」「count」「max/min」「sum」などのメソッドがあります。

ねこ奈
ねこ奈

「max/min」とか「sum」って何だかExcelとかスプレットシートみたいだにゃあ。

講義ではアイテムをMaxまで増殖して、課題ではアイテムを削除します

講義では以下のコードを記述し、ベッドで休むと(厳密に言うとベッドを使用すると)アイテム数をMaxまで増殖させました。

public void onPlayerBed(PlayerBedEnterEvent e){
        Player player = e.getPlayer();
        ItemStack[] itemStacks = player.getInventory().getContents();
        Arrays.stream(itemStacks)
                .filter(item -> !Objects.isNull(item) && item.getMaxStackSize()
                        == 64 && item.getAmount()< 64)
                .forEach(item -> item.setAmount(64));

        player.getInventory().setContents(itemStacks);
    }
初回学習の当時のポスト。今やってもチートですね。
ねこ奈
ねこ奈

Javaコース紹介記事で言っていたアイテム64個増殖させるチートってこの回だったのにゃ!

モナ
モナ

課題は逆に「スタック数がMaxじゃないものは消えるように」することです。

当時の私はこの課題をしないで次へ進んでいました。

なので、再挑戦である今回は課題に挑戦しました。

課題:StreamAPIを用いてスタック数がMaxじゃないものは消えるようにしてみてください。

 @EventHandler
    public void onPlayerBed(PlayerBedEnterEvent e){
        Player player = e.getPlayer();

    //プレイヤーの持ち物(インベントリ)を取得
        Inventory inventory = player.getInventory();

    //インベントリの中身を取得
        ItemStack[] itemStacks = inventory.getContents();

       
 //インベントリの中身を一つずつ見る
        Arrays.stream(itemStacks)
        // アイテムがnullでなく、最大スタックサイズが64で、現在のアイテム数が64未満の場合
        .
filter(item -> !Objects.isNull(item)  
                && item.getMaxStackSize() == 64 && item.getAmount() < 64)
        // そのアイテムをインベントリから削除
                .forEach(inventory::remove);
    }
ねこ奈
ねこ奈

講義のコードと若干違うのにゃ。こんな面倒なことしなくても「.setAmount()」の括弧を「0」にするだけでいいんじゃないのかにゃ?

モナ
モナ

それだとアイテムを「見えなくする」ことにはなりますが、インベントリからアイテムを「削除」するわけではありません。

また、特定の条件を満たしたアイテムを削除するための適したメソッドがないため、講義では
「ItemStack[] itemStacks = player.getInventory().getContents();」と一行にしていたコードを、「inventory 」を宣言して「.forEach()」の括弧内でアイテム削除の指示を出せるようにするため

「 Inventory inventory = player.getInventory(); 

ItemStack[] itemStacks = inventory.getContents();」と2行に分けています。

ねこ奈
ねこ奈

…ここまで自力で解決できたのにゃ?

もなぁ
もなぁ

まさか!

ChatGPTに色々とコードの意味を聞いて、課題に関する質問を15回以上して、色々試行錯誤してコードを作成しています…かれこれ3時間は課題に時間を費やしました(T_T)

まとめ  初回は課題をスルーした説浮上

今回はStreamAPIとラムダ式について、より深く内容を学びました。

ハック
ハック

今回StreamAPIとラムダ式について色々と調べたのですが、前回はなんとなく講義のコードを真似して満足し、課題が全然分からず次に進んだような気がします。

こんな調子で上級編の課題に挑めるのでしょうか…初級編をしっかりと学び直しします。

以上で今回の学習記録を終えます。

ここまでご覧いただきありがとうございました。

コメント

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