この記事では、Java 8で導入された新しいレベルの抽象化を習得するのに役立つ4つの点に焦点を当てます。
1.これ以上のサイクル
これは以前にも言ったことがありますが、もう一度言います。 ループに別れを告げ、Stream APIを歓迎します。 要素ループに関するJavaの議論の日々は終わりに近づいています。 JavaのStream APIを使用すると、これを達成する方法について話すのではなく、取得したいことを言うことができます。
次の例を見てみましょう。
記事のリストがあり、それぞれに独自のタグリストがあります。 ここで、「Java」タグを含む最初の記事を取得します。
標準的なアプローチをご覧ください。
public Article getFirstJavaArticle() { for (Article article: articles) { if (article.getTags().contains("Java")) { return article; } } return null; }
Stream APIを使用して問題を解決しましょう。
public Optional<Article> getFirstJavaArticle() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .findFirst(); }
かなりクールですよね?
最初にfilterを使用してJavaタグを含むすべての記事を検索し、次にfindFirstを使用して最初のインクルードを取得します。
問題は、最初の包含のみが必要な場合に、なぜリスト全体をフィルタリングする必要があるのかということです。 ストリーム...は遅延型であり、 filterはストリームを返すため、最初の包含が見つかるまで計算が行われます。
以前、ループをストリームAPIに置き換えることに関する記事を書きました 。 他の例が必要な場合は読んでください。
2. nullチェックを取り除く
前の例では、 オプションの<Article>を返すことができます。
オプションは、ゼロ以外の値を含む場合と含まない場合があるオブジェクトコンテナです。
このオブジェクトにはいくつかの高階関数があり、 null / notNullのチェックを繰り返す必要がなくなり、やりたいことに集中することができます。
次に、 getFirstJavaArticleメソッドを改善します。 Javaの記事がない場合は、最新の記事を喜んで受け取ります。
典型的なソリューションがどのように見えるか見てみましょう。
Article article = getFirstJavaArticle(); if (article == null) { article = fetchLatestArticle(); }
そして、 オプションの<T>を使用したソリューションです。
getFirstJavaArticle() .orElseGet(this::fetchLatestArticle);
よさそうですね。
追加の変数、 if構文、またはnullの言及はありません。 Optional.orElseGetを使用して、値が見つからない場合に取得する内容を指定します。
Optionalを使用した別の例を見てください。 最初のJava記事が見つかった場合にその名前を取得するとします。
繰り返しますが、一般的なソリューションを使用すると、nullチェックを追加する必要がありますが、...何を知っていますか? この日を保存するためのオプションです。
playGround.getFirstJavaArticle() .map(Article::getTitle);
ご覧のとおり、 Optionalは高次マップ関数を実装し、結果に関数を適用するのに役立ちます(ある場合) 。
オプションの詳細については、 ドキュメントを参照してください。
3.高階関数を作成する
ご覧のとおり、Java 8には既製の高階関数のセットが付属しており、それらを使って驚異的な作業を行うことができます。 しかし、なぜそこで停止するのですか? そして、独自の高階関数を作成してみませんか?
高階関数を作成するために必要な唯一のことは、Java機能インターフェイスの1つ、またはSAMタイプのインターフェイスを引数として取り、それらの1つを返すことです。
これを説明するために、次のシナリオを検討してください。
さまざまな種類のドキュメントを印刷できるプリンターがあります。 印刷する前に、プリンタはウォームアップし、印刷後にスリープモードになります。
次に、オンとオフの手順を気にせずにコマンドをプリンターに送信できるようにします。 これは、高次関数を作成することで解決できます。
public void print(Consumer<Printer> toPrint) { printer.prepare(); toPrint.accept(printer); printer.sleep(); }
ご覧のとおり、機能インターフェースの1つであるConsumer <Printer>を引数として使用します。 次に、この機能を起動手順とシャットダウン手順の間のステップとして実行します。
これで、印刷したいもの以外を心配することなく簡単にプリンターを使用できます。
// printHandler.print(p -> p.print(oneArticle)); // printHandler.print(p -> allArticles.forEach(p::print));
より詳細な例については、TransactionHandlerの作成方法に関する私の記事を読んでください。
4.複製に注意してください。 乾燥原理
関数の作成は簡単かつ迅速です。 ただし、簡単なコード記述では、複製が必要になります。
次の例を考えてみましょう。
public Optional<Article> getFirstJavaArticle() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .findFirst(); }
この方法はかつて私たちに役立ちましたが、普遍的ではありません。 一般に、他のタグと要件に基づいて記事を見つけることができる方法が必要です。
新しいスレッドを作成することは非常に魅力的です。 彼らはとても小さくて簡単に作ることができますが、どのように傷つけることができますか? 対照的に、コードの小さなセクションは、DRYの原則をさらに動機付ける必要があります。
コードを再編成しましょう。 まず、 getFirstJavaArticle関数をより汎用的にします-必要なものに応じて記事をフィルター処理するための引数として述語を取ります。
public Optional<Article> getFirst(Predicate<Article> predicate) { return articles.stream() .filter(predicate) .findFirst(); }
次に、この関数を使用して、いくつかの異なる記事を取得してみましょう。
getFirst(article -> article.getTags().contains("Java")); getFirst(article -> article.getTags().contains("Scala")); getFirst(article -> article.getTitle().contains("Clojure"));
それでも、まだ重複コードを扱っています。 同じ述部が異なる値に使用されていることがわかります。 Functionインターフェースを介してこれらの重複を削除してみましょう。 これで、コードは次のようになります。
Function<String, Predicate<Article>> basedOnTag = tag -> article -> article.getTags().contains(tag); getFirst(basedOnTag.apply("Java")); getFirst(basedOnTag.apply("Scala"));
いいね! このコードはDRYの原則に準拠していると言えますか?
この記事がお役に立てば幸いです。また、Java 8と機能的な機能を使用して、より高い抽象化レベルで何ができるかについてのアイデアを提供してください。