こんにちは!運営者のハックです。
今回は「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とラムダ式について色々と調べたのですが、前回はなんとなく講義のコードを真似して満足し、課題が全然分からず次に進んだような気がします。
こんな調子で上級編の課題に挑めるのでしょうか…初級編をしっかりと学び直しします。
以上で今回の学習記録を終えます。
ここまでご覧いただきありがとうございました。
コメント