JavaRush /Java Blog /Random-JA /本の翻訳。Java での関数型プログラミング。第1章
timurnav
レベル 21

本の翻訳。Java での関数型プログラミング。第1章

Random-JA グループに公開済み
間違いを見つけて翻訳の品質を向上させるために喜んでお手伝いさせていただきます。私は英語力を向上させるために翻訳をしていますが、読んで翻訳の間違いを見つければ、私よりもさらに上達するでしょう。この本の著者は、この本は Java の使用経験が豊富であることを前提としていると書いていますが、正直に言うと、私自身はそれほど経験はありませんが、本の内容は理解できました。この本では、指で説明するのが難しいいくつかの理論を扱っています。Wiki に適切な記事がある場合は、そこへのリンクを提供しますが、よりよく理解するには、自分で Google で検索することをお勧めします。みんなに幸運を。:) 私の翻訳を修正したい人や、ロシア語で読むのが下手だと思う人のために、ここから原文をダウンロードできます。 目次 第 1 章 こんにちは、ラムダ式 - 現在読んでいます 第 2 章 コレクションの使用 - 開発中 第 3 章 文字列、コンパレータ、フィルター - 開発中 第 4 章 ラムダ式を使用した開発 - 開発中 第 5 章 リソースの操作 - 開発中 第 6 章 怠惰になる - 開発中開発 第 7 章 リソースの最適化 - 開発中 第 8 章 ラムダ式を使用したレイアウト - 開発中 第 9 章 すべてをまとめる - 開発中

第 1 章 こんにちは、ラムダ式!

私たちの Java コードは、驚くべき変革に向けて準備が整っています。私たちが行う日常業務は、よりシンプル、簡単、より表現力豊かなものになります。Java をプログラミングする新しい方法は、他の言語でも何十年にもわたって使用されてきました。Java に対するこれらの変更により、エラーが少なく、簡潔でエレガントで表現力豊かなコードを書くことができます。これを使用すると、標準を簡単に適用し、少ないコード行で共通の設計パターンを実装できます。この本では、私たちが毎日行う問題の簡単な例を使用して、プログラミングの関数型スタイルを探ります。このエレガントなスタイルと新しいソフトウェア開発方法について詳しく説明する前に、それが優れている理由を見てみましょう。
考え方を変える
命令型スタイルは、Java 言語の誕生以来、Java が私たちに与えてくれたものです。このスタイルは、言語に実行させたいことのすべてのステップを Java に記述し、それらのステップが忠実に実行されていることを確認するだけであることを示唆しています。これはうまくいきましたが、まだレベルが低いです。コードが冗長になりすぎたので、もう少しインテリジェントな言語が必要になることがよくありました。そうすれば、それを宣言的に言うことができます。 つまり、それを行う 方法を深く掘り下げる必要はありません。開発者のおかげで、Java がこれを支援できるようになりました。これらのアプローチの利点と違いを理解するために、いくつかの例を見てみましょう。
いつものやり方
2 つのパラダイムの実際の動作を確認するために、よく知られた基本から始めましょう。これは命令型メソッドを使用して都市コレクション内のシカゴを検索します。この本のリストにはコード スニペットのみが表示されます。 boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); コードの命令型バージョンはノイズが多く (この単語と何の関係があるのでしょうか?)、低レベルであり、変更可能な部分がいくつかあります。 まず、 foundと呼ばれるこの臭いブール値フラグを作成し、次にコレクション内の各要素を反復処理します。探している都市が見つかった場合は、フラグを trueに設定してループを終了します。最後に、検索結果をコンソールに出力します。
もっと良い方法があります
注意深い Java プログラマであれば、このコードをひと目見るだけで、次のように、より表現力豊かで読みやすいものに変えることができます。 以下に System.out.println("Found chicago?:" + cities.contains("Chicago")); 宣言型スタイルの例を示します。contains() メソッドは、必要なものに直接アクセスするのに役立ちます。
実際の変化
これらの変更により、コードにかなりの量の改善がもたらされます。
  • 変更可能な変数を使用する必要はありません
  • ループの反復は内部に隠されています
  • コードの煩雑さを軽減
  • コードがより明確になり、注意が集中します
  • インピーダンスが低い。コードがビジネスの意図を厳密に追跡している
  • エラーの可能性が少なくなる
  • 理解しやすくサポートしやすくなる
単純なケースを超えて
これは、コレクション内の要素の存在をチェックする宣言関数の簡単な例であり、Java では長い間使用されてきました。ここで、ファイルの解析、データベースの操作、Web サービスのリクエスト、マルチスレッドの作成など、より高度な操作を実行するための命令型コードを作成する必要がないことを想像してみてください。Java では、単純な操作だけでなくアプリケーション全体にわたって、間違いを犯しにくくする簡潔で洗練されたコードを作成できるようになりました。
昔ながらのやり方
別の例を見てみましょう。価格を含むコレクションを作成しており、すべての割引価格の合計を計算するためにいくつかの方法を試します。 10% 割引して、20 ドルを超えるすべての価格を合計するように求められたとします。まずは通常の Java の方法でこれを実行してみましょう。 このコードは私たちにとって非常に馴染みのあるものです。まず、結果の値を格納する可変変数 totalOfDiscountedPricesを作成します。次に、価格コレクションをループし、20 ドルを超える価格を選択し、割引価格を取得して、その値を totalOfDiscountedPricesに追加します。最後に、割引を考慮したすべての価格の合計が表示されます。以下はコンソールに出力される内容です final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
割引価格の合計: 67.5
動作しますが、コードが乱雑に見えます。しかし、それは私たちのせいではありません、私たちは利用可能なものを使用しました。 コードは非常に低レベルです。プリミティブ (Google で調べてください。興味深いものです) へのこだわりがあり、単一責任の原則に反するものです。在宅で仕事をしている私たち人間は、そのようなコードをプログラマーを目指す子供たちの目から遠ざけるべきです。子供たちの脆弱な心を警告し、「これが生き残るためにしなければならないことなのか?」という質問に備える必要があります。
もっと良い方法があります、別の方法です
今ではもっと良く、もっと良くできるようになりました。私たちのコードは仕様要件に似ている可能性があります。これにより、ビジネス ニーズとそれを実装するコードとの間のギャップが減り、要件が誤解される可能性がさらに低くなります。変数を作成して繰り返し変更するのではなく、次のリストのように、より高い抽象レベルで作業してみましょう。 final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); 声に出して読み上げてみましょう - 価格フィルターは 20 より大きく、「価格」キーを使用してマップ (「キー」「値」ペアを作成)、割引を含む価格を作成し、それらを追加します。
- 翻訳者のコメントとは、コードを読んでいるときに頭の中に浮かんだ単語を意味します 。filter(price ->price.compareTo(BigDecimal.valueOf(20)) > 0)
コードは、読んだのと同じ論理シーケンスで一緒に実行されます。コードは短縮されましたが、Java 8 の新しい機能を大量に使用しました。まず、 価格リストで stream() メソッドを呼び出しました。これにより、後で説明する便利な機能の豊富なセットを備えたカスタム イテレータへの扉が開きます。 価格リスト内のすべての値を直接ループする代わりに、 filter()map()などのいくつかの特別なメソッドを使用します。Java や JDK で使用したメソッドとは異なり、これらのメソッドは匿名関数 (ラムダ式) をかっこ内のパラメータとして受け取ります。後で詳しく勉強します。 reduce()メソッドを呼び出すことで、 map()メソッドで得られた値(割引価格)の合計を計算します。ループは contains()メソッドを使用したときと同じ方法で非表示になります。 ただし、 filter()メソッドと map()メソッドはさらに複雑です。 価格リストの価格ごとに、渡されたラムダ関数を呼び出し、それを新しいコレクションに保存します。 このコレクションに対してreduce()メソッドが呼び出され、最終結果が生成されます。以下はコンソールに出力される内容です
割引価格の合計: 67.5
変更点
通常の方法と比較した変更点は次のとおりです。
  • コードは見た目が良く、乱雑ではありません。
  • 低レベルの操作はありません
  • ロジックの改善や変更が容易になる
  • 反復はメソッドのライブラリによって制御されます
  • 効率的な遅延ループ評価
  • 必要に応じて並列化が容易になる
Java がこれらの改善をどのように提供するかについては後で説明します。
ラムダが助けてくれます:)
Lambda は、命令型プログラミングの煩わしさから私たちを解放するための関数キーです。Java の最新機能を使用してプログラムの方法を変えることで、エレガントで簡潔なだけでなく、エラーが発生しにくく、より効率的で、最適化、改善、マルチスレッド化が容易なコードを作成できます。
関数型プログラミングで大きな利益を得る
関数型プログラミング スタイルでは、信号対雑音比が 高くなります。記述するコード行は少なくなりますが、各行または式はより多くの機能を実行します。命令型と比較して、関数型バージョンのコードから得られるものはほとんどありません。
  • エラーの原因となり、異なるスレッドからのコードを同時に処理することが困難になる、変数の不要な変更や再割り当てを回避しました。命令型バージョンでは、ループ全体でtotalOfDiscountedPrices変数に異なる値を設定します。機能バージョンでは、コード内の変数に明示的な変更はありません。変更が少ないほど、コード内のバグも少なくなります。
  • コードの機能バージョンは並列化が容易です。たとえ、map() メソッドの計算が長かったとしても、何も心配することなく並列実行できます。別のスレッドから命令型コードにアクセスする場合は、 totalOfDiscountedPrices変数も同時に変更することを考慮する必要があります。機能バージョンでは、すべての変更が行われた後にのみ変数にアクセスするため、コードのスレッドの安全性を心配する必要がなくなります。
  • コードはより表現力豊かになります。ダミー値を使用した変数の作成と初期化、価格リストのループ処理、変数への割引価格の追加など、いくつかのステップでコードを実行する代わりに、リストの map() メソッドに別のリストを返すよう単純に要求します。割引価格を計算して合計します
  • 関数型スタイルはより簡潔です。命令型バージョンよりも必要なコード行が少なくなります。コードがよりコンパクトになると、書く作業や読む作業が減り、保守が容易になります。
  • コードの機能バージョンは、構文を理解すれば直観的で理解しやすいものです。以下の図に示すように、map()メソッドは、渡された関数 (割引価格を計算する) をコレクションの各要素に適用し、その結果を含むコレクションを生成します。

図 1 - マップ メソッドは、渡された関数をコレクションの各要素に適用します
ラムダ式のサポートにより、Java の関数型プログラミングの力を最大限に活用できます。このスタイルをマスターすれば、変更やエラーを減らして、より表現力豊かで簡潔なコードを作成できます。以前は、Java の主な利点の 1 つは、オブジェクト指向パラダイムのサポートでした。そして、機能的なスタイルは OOP と矛盾しません。命令型プログラミングから宣言型プログラミングへの移行における真の卓越性。Java 8 を使用すると、関数型プログラミングとオブジェクト指向スタイルを非常に効果的に組み合わせることができます。引き続き、OO スタイルをオブジェクト、そのスコープ、状態、関係に適用できます。さらに、動作や変化の状態、ビジネスプロセス、データ処理を一連の機能セットとしてモデル化できます。
なぜ関数型スタイルでコーディングするのでしょうか?
プログラミングの関数型スタイルの全体的な利点を見てきましたが、この新しいスタイルは学ぶ価値があるでしょうか? これは言語における小さな変化でしょうか、それとも私たちの生活を変えるのでしょうか? 時間とエネルギーを無駄にする前に、これらの質問に対する答えを見つけなければなりません。Java コードの記述はそれほど難しくなく、言語の構文は単純です。私たちは使い慣れたライブラリと API に慣れています。コードの作成と保守に本当に労力を必要とするのは、開発に Java を使用する典型的なエンタープライズ アプリケーションです。私たちは、同僚のプログラマーがデータベースへの接続を正しいタイミングで閉じること、必要以上にデータベースを保持したりトランザクションを実行したりしないこと、例外を適切なレベルで完全にキャッチすること、ロックを適切に適用および解放することを確認する必要があります。 ...このシートは非常に長く続けることができます。上記のそれぞれの議論は単独では重みがありませんが、固有の実装の複雑さと組み合わせると、圧倒的で時間がかかり、実装が困難になります。これらの複雑さを、適切に管理できる小さなコードにカプセル化できたらどうなるでしょうか? そうすれば、標準の実装に常にエネルギーを費やす必要がなくなります。これは大きな利点となるため、機能的なスタイルがどのように役立つかを見てみましょう。
ジョーは尋ねます。
短い * コードとは、単にコード文字数が少ないことを意味するのでしょうか?
*ここでは、ラムダ式を使用したコードの関数スタイルを特徴づけるconcise という言葉について話しています。
この文脈では、コードは余分なものを省き、簡潔であり、より効果的に意図を伝えるために直接的な影響を減らすことを目的としています。これらは広範囲にわたるメリットです。コードを書くことは、材料をまとめるようなものであり、コードを簡潔にすることは、それにソースを加えるようなものです。場合によっては、そのようなコードを記述するのにさらに労力がかかることがあります。読み取るコードは少なくなりますが、コードの透明性は高まります。コードを短縮するときは、コードを明確にすることが重要です。簡潔なコードはデザインのトリックに似ています。このコードでは、タンバリンを持って踊る必要が少なくなります。これは、アイデアをすぐに実装し、うまくいったら次に進み、期待に応えられない場合は放棄できることを意味します。
ステロイドの反復
イテレータを使用してオブジェクトのリストを処理したり、セットやマップを操作したりします。Java で使用するイテレータは私たちにとって馴染みのあるものであり、原始的ではありますが、単純ではありません。多くのコード行を必要とするだけでなく、記述するのも非常に困難です。コレクションのすべての要素をどのように反復処理するのでしょうか? for ループを使用できます。コレクションからいくつかの要素を選択するにはどうすればよいでしょうか? 同じ for ループを使用しますが、コレクションの何かと比較する必要がある追加の変更可能な変数を使用します。それでは、特定の値を選択した後、最小値、最大値、または平均値などの単一の値に対してどのように演算を実行するのでしょうか? 再び循環し、再び新しい変数が登場します。これは、「森のせいで木が見えない」ということわざを思い出させます(原文では反復に関連した言葉遊びが使用されており、「すべてが引き受けられますが、すべてが成功するわけではありません」を意味します - 訳者注)。jdk は、さまざまなステートメントに対して内部イテレータを提供するようになりました。ループを簡素化するためのイテレータ、必要な結果の依存関係をバインドするためのイテレータ、出力値をフィルタリングするためのイテレータ、値を返すためのイテレータ、および最小値、最大値、平均値などを取得するための便利な関数がいくつかあります。さらに、これらの操作の機能は非常に簡単に組み合わせることができるため、さまざまな操作のセットを組み合わせて、より簡単に、より少ないコードでビジネス ロジックを実装できます。完了すると、問題に必要な順序で論理的な解決策が作成されるため、コードが理解しやすくなります。この本の第 2 章以降で、そのようなコードの例をいくつか見ていきます。
アルゴリズムの適用
アルゴリズムはエンタープライズ アプリケーションを駆動します。たとえば、権限チェックを必要とする操作を提供する必要があります。私たちは取引が迅速に完了し、小切手が正しく完了することを確認する必要があります。このようなタスクは、多くの場合、以下のリストに示すように、非常に一般的な方法にまとめられます。 Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); このアプローチには 2 つの問題があります。まず、多くの場合、これにより開発作業が 2 倍になり、アプリケーションの保守コストの増加につながります。第 2 に、このアプリケーションのコード内でスローされる可能性のある例外を見逃す可能性が非常に高いため、トランザクションの実行とチェックの通過が危険にさらされます。適切な try-finally ブロックを使用できますが、誰かがこのコードに触れるたびに、コードのロジックが壊れていないかを再チェックする必要があります。そうでないと、工場を放棄してコード全体をひっくり返してしまう可能性があります。トランザクションを受信する代わりに、以下のコードのように、適切に管理された関数に処理コードを送信することもでき、 runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); これらの小さな変更を積み重ねることで、大幅な節約につながります。ステータス チェックとアプリケーション チェックのアルゴリズムには新しいレベルの抽象化が与えられ、 runWithinTransaction()メソッドを使用してカプセル化されます。このメソッドでは、トランザクションのコンテキストで実行されるコードの一部を配置します。何かをし忘れたことや、適切な場所で例外をキャッチしたかどうかを心配する必要はもうありません。アルゴリズム関数がこれを処理します。この問題については、第 5 章で詳しく説明します。
アルゴリズム拡張
アルゴリズムはますます頻繁に使用されていますが、エンタープライズ アプリケーション開発で完全に使用するには、アルゴリズムを拡張する方法が必要です。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION