[Java] Getting Started with Lambda Expressions

原文はこちら。
https://community.oracle.com/docs/DOC-1003597

ラムダ式(lambda expressions)によってコードの行数がどれほど削減でき、メンテナンスが簡単になるのかをご覧ください。

「ラムダ式って何?」という質問への回答のために、システムの挙動を定義する式としてラムダのことを考えることができます。わずかなラムダ式の構文により、Javaプログラミング言語に対し簡潔で柔軟なコードを記述するための効率の良いツールをもたらします。
コードの行数を減らすことができるだけでなく、ラムダ式はモジュール性を持ち、ソフトウェアシステムに対し拡張性の特徴をもたらす関数型プログラミングのイディオムです。
ラムダ式の利用は、小さな、具体的なシステムの挙動を定義するために多大な構文を持つ匿名クラスを記述するよりもよい代替手段です。ラムダ式は特定のイベントでの実行アクションを記述するイベントハンドラを定義するためのリーンアプローチを提供します。ある典型的な例では、イベントハンドラをSwingフレームワークで必要としている場合、イベントハンドラインターフェースのための実装を提供する必要があります。フレームワークが関数型プログラミングの特徴に沿って設計されている場合にのみ、ラムダ式を活用することができます。

ラムダ式を理解するために、2つの微妙なニュアンスを理解する必要があります。
  • 関数型インターフェース(Functional interfaces)
    1個のメソッド定義だけを持つJavaインターフェース。このようなインターフェースの固有の性質により、1個のタスクのみ実行するメソッドを持つことができる。このインターフェースへの任意の参照は宣言された唯一のメソッドだけが処理する。
  • 式(Expressions)
    インターフェース実装を定義する方法。関数型インターフェースは抽象メソッドを持ち、式はメソッドが実施することを定義する。
では、Java関数型プログラミングの様々な特徴を見ていきましょう。

The Syntax of Functional Interfaces

Listing 1 から Listing 3 で、関数型インターフェースで利用可能な構文を示しています。
Listing 1. Interface definition 1
public interface Event {
   public void onEventOccurance();
}
Listing 2. Interface definition 2
public interface EventProcess {
   public void onEventOccuranceProcess(int i);
}
Listing 3. Interface definition 3
public interface EventResult {
   public int onEventOccuranceConfirm(int i);
}

The Syntax of Lambda Expressions


ラムダ式の構文は以下の3パートから構成されています。
  • 括弧()に囲まれたカンマ区切りのパラメータ・リスト
    メソッドへのパラメータが1個のみの場合、括弧を省略できる。
  • アロー演算子(->
    パラメータとラムダ式本体のセパレータ
  • ラムダ式本体
    1個のステートメントもしくはステートメント・ブロックを含む。1個のステートメントの場合、中括弧は不要だが、ステートメント・ブロックの場合は中括弧で囲む必要がある。
ラムダ式は匿名クラスとは異なり、辞書的にスコープを持ち、シャドーイング問題がない傾向にあります(シャドーイング問題については、Java Tutorialsをご覧ください)。
Shadowing
http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html#shadowing
ラムダ式がアクセスする変数はfinalもしくは実際上finalであるべきゆえに、ラムダ式はシャドーイング問題から安全です。もし変数がこれらの条件を満足しない場合、コンパイル時にエラーが発生します。
ラムダ式の構文をListing 4からListing 7で示します。
Listing 4のラムダ式はListing 1の関数型インターフェース定義に対応しています。Listing1の onEventOccurance() メソッドはパラメータをとらないので、括弧の中身は空です。
Listing 4. Lambda expression 1
() -> System.out.println("event occurred : no argument syntax");
Listing 5のラムダ構文はListing 2の関数型インターフェース定義に対応しますが、Listing 2の onEventOccuranceProcess(int i) メソッドは1個のパラメータをとるため、構文でパラメータ名を宣言しています。インターフェースやラムダ式のパラメータ名は任意で、例えば iapなどをとることができます。名前は式とインターフェースで異なる場合があります。
Listing 5. Lambda expression 2
(i) -> System.out.println("event processed : one argument syntax"+ i);
Listing 6のラムダ構文もまたListing 2の関数型インターフェース定義に対応するものですが、こちらでは別の構文を示しています。
Listing 6. Lambda expression 3, which is an alternative to lambda expression 2
(i) -> {
  System.out.println("event processed : one argument syntax"+ i);
}
Listing 7のラムダ構文はListing 3の関数型インターフェース定義に対応するものです。Listing 3の onEventOccuranceConfirm(int i) メソッドは1個のパラメータをとるため、構文ではパラメータ名を宣言しています。パラメータ名は任意で、例えば iapなどをとることができます。Listing 7の実装は値を返します。 return ステートメントを含めない場合、ラムダ式がインターフェース契約に反するため、コンパイルエラーが発生します。
Listing 7. Lambda expression 4
(i) -> {
  i = i + 1;
  return i;
}

Invoking a Functional Interface


Javaクラスを見ていきましょう。このクラスは、ラムダ式を利用する関数型インターフェースメソッドを呼び出します。Listing 8のEventOrganizer クラスには関数型インターフェースをパラメータとして受け取り、メソッドを呼び出すメソッドがあります。
Listing 8. Class that invokes functional interface methods
package com.j2se8.samples.event;
 
import com.j2se8.samples.event.funct.Event;
import com.j2se8.samples.event.funct.EventProcess;
import com.j2se8.samples.event.funct.EventResult;
 
public class EventOrganizer {
  public void testEvent(Event event) {
    event.onEventOccurance();
  }
  public void testEventProcess( EventProcess eventProcess)  {
    eventProcess.onEventOccuranceProcess(1);
  }
  public void testEventResult( EventResult eventResult ) {
    int result = eventResult.onEventOccuranceConfirm(1);
    // printing the return value of the evaluated expression
    System.out.println("Print result value : " + result);
  }
}
Listing 9は、EventOrganizer オブジェクトを呼び出し、入力値の処理のためにラムダ式を渡すJavaクライアントを示しています。 この例では、ビジネスロジックをデータが存在する場所に渡していることがわかります。
Listing 9. Java client
package com.j2se8.samples.event;

public class EventClient {
  public static void main(String[] args) {
    EventOrganizer eventTester = new EventOrganizer();

    // calling no-argument functional interface
    eventTester.testEvent(() -> System.out.println("event occurred : no argument syntax"));

    // calling single-argument method of functional interface
    eventTester.testEventProcess((i) -> System.out.println("event processed : one argument syntax : " + i));

    // calling alternative single-argument method of functional interface syntax of omitting parentheses
    eventTester.testEventProcess(i -> System.out.println("event processed : one argument syntax : " + i));

    // calling alternative single-argument method of functional interface syntax of wrapping expression body with curly braces
    eventTester.testEventProcess((i) -> {
       System.out.println("event processed : one argument syntax : " + i);
    });

    // calling single-argument method of functional interface that returns the result
    eventTester.testEventResult((i) -> {
       i = i + 1;
       return i;
    });
  }
}

Rethinking the Way We Program


ラムダ式を使う場合、プログラムの書き方を再検討する必要に迫られます。むしろデータを保持し、どの操作が当該データに対して許されているかを定義するAPIメソッドを公開するJavaクラスを有しているといったほうがよいでしょう。操作のセマンティクスとロジックを、必要に基づいてこうしたメソッドに注入することができます。これにより、同じデータをさまざまな方法、様々な目的のために解釈し利用することができます。Listing 10でこの要点を理解することができることでしょう。

種々の処理目的のために異なるラムダ式を同じメソッドに渡すことができる様子を見ていきましょう。Listing 10ではEventDispatcherという名前のクラスを定義しており、このクラスは、必要に応じて登録、処理できる種々のイベントのリストを保持しています。

Listing 10. Program with lambda expression being sent to the same method
package com.j2se8.samples.event;

import java.util.ArrayList;
import java.util.List;
import com.j2se8.samples.event.funct.Event;
import com.j2se8.samples.event.funct.EventProcess;
import com.j2se8.samples.event.funct.EventResult;

public class EventDispatcher {    
  private List<Event> eventList = new ArrayList<Event>();
  private List<EventProcess> eventProcessList = new ArrayList<EventProcess>();  
  private List<EventResult> eventResultList = new ArrayList<EventResult>();    

  public void registerEventHandler(Event event) {
    eventList.add(event); 
  }    
  public void registerEventProcessHandler(EventProcess eventProcess) {
    eventProcessList.add(eventProcess);
  }
  public void registerEventResultHandler(EventResult eventResult) {
    eventResultList.add(eventResult);
  }
  public void dispatchEvent() {
    for (Event event : eventList) {
      event.onEventOccurance();
    }
  }

  public void processEvents() {
    int i = 1;
    for(EventProcess process : eventProcessList) {
      process.onEventOccuranceProcess(i++);
    }
  }

  public void compute() {
    final int i = 2;
    for(EventResult process : eventResultList) {
      int result = process.onEventOccuranceConfirm(i);
      System.out.println("return result : "+ result);
    }
  }
}

ではクライアント側を見ていきます(Listing 11)。このクライアントはイベントとして式を登録し、EventDispatcherを呼び出して登録された式に基づいて振る舞います。
Listing 11. Client that registers multiple expressions as events
package com.j2se8.samples.event;

import com.j2se8.samples.event.funct.Event;
import com.j2se8.samples.event.funct.EventProcess;
import com.j2se8.samples.event.funct.EventResult;

public class EventDispatcherClient {
  public static void main(String[] args) {
    EventDispatcher eventDispatcher = new EventDispatcher();

    // define no-argument functional interface
    Event event1 = () -> System.out.println("event 1 occurred : no argument syntax");
    Event event2 = () -> System.out.println("event 2 occurred : no argument syntax");

    // define single-argument method of functional interface
    EventProcess process1 = (i) -> System.out.println("event processed : one argument syntax : "+i);

    // define alternative single-argument method of functional interface syntax of omitting parentheses
    EventProcess process2 = i -> System.out.println("event processed : one argument syntax : "+i);

    // define alternative single-argument method of functional interface syntax of wrapping expression body with curly braces     
    EventProcess process3 = (i) -> {
      System.out.println("event processed : one argument syntax : "+i);
    };
  
    // define single-argument method of functional interface that returns the result
    EventResult result1 = (i) -> { 
      i = i + 2;
      return i;
    };
    EventResult result2 = (i) -> {
      i = i - 2;
      return i;
    };
    EventResult result3 = (i) -> {
      i = i * 2;
      return i;
    };
    EventResult result4 = (i) -> {
      i = i / 2;
      return i;
    };
    EventResult result5 = (i) -> {
      i = i % 2;
      return i;
    };

    // registering events     
    eventDispatcher.registerEventHandler(event1);
    eventDispatcher.registerEventHandler(event2);
    eventDispatcher.dispatchEvent();
    System.out.println("");

    // registering event processes     
    eventDispatcher.registerEventProcessHandler(process1);
    eventDispatcher.registerEventProcessHandler(process2);
    eventDispatcher.registerEventProcessHandler(process3);
    eventDispatcher.processEvents();
    System.out.println("");

    // registering result-based events     
    eventDispatcher.registerEventResultHandler(result1);
    eventDispatcher.registerEventResultHandler(result2);
    eventDispatcher.registerEventResultHandler(result3);
    eventDispatcher.registerEventResultHandler(result4);
    eventDispatcher.registerEventResultHandler(result5);
    eventDispatcher.compute();
  }
}

Conclusion

ラムダ式を独立して構築できること、呼び出し元のクラスから呼び出し先のクラスに注入することができることをご覧いただきました。上記の例でわかるように、ラムダ式はインタフェースの実装を提供します。それゆえ、ラムダ式は無名クラス以上に単純な実装として機能することができます。これにより、コード量が劇的に削減されるためメンテナンスしやすいコードになり、最小限の影響にとどめて変更を導入することができます。ラムダ式の構文は、Javaプログラマに非常に目新しいものゆえに、この構文が当たり前のものとなって、Javaコミュニティにラムダ式の詳細知識が蓄えられるまでは、プログラマにとって困惑のたねとなることでしょう。
ラムダ式はJava 8で導入された新機能で、主流のプログラミングになるまでには時間がかかることでしょう。しかし、このプログラミングアプローチは新たなプログラミング・スタイルを開き、プログラムの構築方法を変化させる可能性があります。このエントリで、ラムダ式の利用に関するいくつかの特徴を説明、紹介して参りましたが、まだまだたくさんありますので、是非ご自身でこのトピックを深めてください。

See Also

About the Author

Mohan BasavarajappaはInfosysのテクニカル・アーキテクトです。Java SE、Java EE、Oracle WebCenter ContentやOracle WebCenter Sitesのようなエンタープライズ・コンテンツ管理テクノロジーに興味関心を持っています。以前は、オープンソーステクノロジーを活用し、組織全体で再利用可能なライブラリを作成することで、生産性の向上と市場投入までの時間を短縮することを目的としたReuse Practitionerの役割を担っていました。オープンソースの愛好家であり、オープンソースフレームワークやライブラリの調査・探索が大好きな、アクティブなOracle Technology Networkメンバーです。
MOHAN BASAVARAJAPPAのプロフィール
https://community.oracle.com/people/Mohan+Basavarajappa?customTheme=java

0 件のコメント:

コメントを投稿