業務備忘録

備忘録です

【Java】Stream APIの練習をする

2023-08-27 20:47:49 | 日記

1. Stream APIって?

コレクション(ArrayやArrayListなど)などに格納された大量のデータを効率的に操作できる手段。 for文などを利用して記述するより、スッキリとコレクションへの処理を記述することが可能。 C#で言うところのLINQ(firstOrDefaultとかwhereとかで操作するやつ)。

2. 実際にやってみる

Stream API については記事を複数回に分けて書こうと思います。 まずは準備として以下のパッケージを自作。図書館での貸し出し処理をモデルにします。
コード全体はgitLabに公開。
https://gitlab.com/simulacre1/JavaStudy/-/tree/main/LibraryTest

Book.java

書籍を表現したクラス。

gooBlogの字数の関係で委細省略。
bookName(書籍名)
author(著者名)
lendCount(貸出回数)
lendDate(最終貸出日)
の各フィールドに対するgetter/setterに加え、
貸出回数をインクリメントする素朴なメソッドがを実装しています。
あと貸出履歴を保持するリストとしてhistoryと、historyに履歴を追加するaddHistory()も。

User.java

利用者を表現したクラス。

委細省略。
bookName(書籍名)
userName(利用者名)
date(日付)
をとりあえずフィールドにして、コンストラクタだけ書いてあります。

BookList.java

gooBlogの字数制限の関係でコードは画像です。

DBは使わないのでとりあえず貸出可能書籍管理用のクラス。

Proc.java

処理を行うクラス。
貸出を行う場合はlendProc()を再帰的に呼び出す。
貸出処理の内実は、

①引数の書籍一覧を格納したbookMapから貸出対象の書籍のBookオブジェクトを取得
②貸出回数のインクリメント
③lendDate(最終貸出日)の更新
④addHistory()によって貸出履歴追加

貸出を止めて貸出回数順に書籍を一覧表示するメソッドがsortByLendCount()。後述します。

LendBook.java

mainメソッド。処理の振り分けのみ。

 

3. 実際にやってみる

コンソールから、『こころ』の貸出処理を2回、『斜陽』の貸出処理を1回行ったのち、"2"を入力して貸出回数によるソート処理を呼び出します。

    private void sortByLendCount(Map<String, Book> bookMap) {
        bookMap.entrySet().stream()
            .sorted((s1, s2)->s2.getValue().getLendCount() - s1.getValue().getLendCount())
            .forEach(r ->System.out.println(r.getValue().getBookName() + "は" + r.getValue().getLendCount() + "回貸し出されました"));
    }

StreamAPIを利用するためには、stream()によって大量の処理(=ストリーム処理)の始点を作らなくてはなりませんが、
HashMapのようなmapインターフェースを実装したオブジェクトにはstream()が準備されていません。
なので、HashMapからは、entrySet()※1を使用して、HashedMapをSetに変換したうえでstream()を呼び出し、ストリーム処理を開始します。

肝心の並べ替えは、sorted()によって行います。
sorted()の引数には、2つの要素から計算した結果※2を渡します。

.sorted((s1, s2)->s2.getValue().getLendCount() - s1.getValue().getLendCount())

上記のようにラムダ式の2つ目の引数で取得した要素から、1つ目の引数で取得した要素を引くような記述の場合、

===========================

こころは2回貸し出されました

斜陽は1回貸し出されました

想像の共同体は0回貸し出されました

近代文学論争は0回貸し出されました

ドストエフスキーの詩学は0回貸し出されました

近代天皇像の形成は0回貸し出されました

天皇の肖像は0回貸し出されました

ドストエフスキーの詩学は0回貸し出されました

=============================

と、降順で並べ替えられます。
一方

.sorted((s1, s2)->s1.getValue().getLendCount() - s2.getValue().getLendCount())

と、減算の順番を逆にした場合は、

===============================

想像の共同体は0回貸し出されました

近代文学論争は0回貸し出されました

ドストエフスキーの詩学は0回貸し出されました

近代天皇像の形成は0回貸し出されました

天皇の肖像は0回貸し出されました

ドストエフスキーの詩学は0回貸し出されました

斜陽は1回貸し出されました

こころは2回貸し出されました

==================================

と昇順で並べ替えられます。

この場合、貸し出されていない書籍が上位に表示されて邪魔ですね。そこで、貸出回数が0回の書籍を篩にかけます。

bookMap.entrySet().stream()
            .filter(i->i.getValue().getLendCount() > 0)
            .sorted((s1, s2)->s1.getValue().getLendCount() - s2.getValue().getLendCount())
            .forEach(r ->System.out.println(r.getValue().getBookName() + "は" + r.getValue().getLendCount() + "回貸し出されました"));

==================

斜陽は1回貸し出されました

こころは2回貸し出されました

==================

filter()によって、filter()の引数の条件に合った要素のみに対象を絞り込むことができます。

次はflatMapメソッドの使用などについて書きます。

※1 正確には、entrySet()は、mapのキーと値のセット(Entry)を格納したSet = Set<Map.Entry<K,V>>
を返すので、EntryのgetValueメソッド(mapの値を返す)によって値となるBookオブジェクトを取得しています。
stream()はCollectionインターフェースで定義されているので、SetインターフェースはCollectionを親インターフェースとするが、Mapは親インターフェースとしていない点に差があります。

※2 不正確な記述ですが、きちんと説明するために調査が必要なので説明は別日を期します。