1. ホーム
  2. java

[解決済み] Javaストリームにおいて、peekは本当にデバッグのためだけなのでしょうか?

2022-04-21 02:41:18

質問

Javaストリームについて調べているのですが、新しい発見があります。新しい発見のひとつは peek() という関数があります。私が読んだpeekに関するほとんどすべての本に、この関数はStreamsのデバッグに使うべきと書いてありました。

各アカウントにユーザー名とパスワードフィールドがあり、login() と loggedIn() メソッドがあるストリームがあったとしたらどうでしょう。

また

Consumer<Account> login = account -> account.login();

そして

Predicate<Account> loggedIn = account -> account.loggedIn();

なぜ、こんなにも悪いのでしょうか?

List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount = 
accounts.stream()
    .peek(login)
    .filter(loggedIn)
    .collect(Collectors.toList());

さて、私が見る限り、これはまさに意図したとおりの働きをしています。それは

  • アカウントの一覧を取得します。
  • 各アカウントへのログインを試行する
  • ログインしていないアカウントは除外される
  • ログインしているアカウントを新しいリストに収集します。

このようなことをすると、何か不都合なことがあるのでしょうか?続けてはいけない理由があれば教えてください。最後に、この解決策でない場合はどうすればいいのでしょうか?

オリジナルでは、以下のように .filter() メソッドを使用していました。

.filter(account -> {
        account.login();
        return account.loggedIn();
    })

解決方法は?

ここから得られる重要なポイント

たとえ目先の目的が達成されたとしても、意図しない方法でAPIを使わないこと。 そのやり方では将来壊れる可能性がありますし、将来のメンテナにとっても不明確です。


別個の操作なので、複数の操作に分割しても害はない。 そこで これは、この特定の動作が将来のバージョンのJavaで修正された場合に影響を与える可能性があります。

使用方法 forEach この操作で、メンテナには 意図した の各要素に副作用が発生します。 accounts そして、それを変異させることができる何らかの操作を実行していること。

という意味でも、よりコンベンショナルです。 peek は、終端操作が実行されるまでコレクション全体に対して操作を行わない中間操作ですが forEach は確かに端末操作です。 このようにすると、もし peek と同じ挙動をします。 forEach は、この文脈では

accounts.forEach(a -> a.login());
List<Account> loggedInAccounts = accounts.stream()
                                         .filter(Account::loggedIn)
                                         .collect(Collectors.toList());