本日のお裾分け

日々の開発で得た知識をシェアします。Java/Scala/Ruby/javascript

Java資格取得(OCJP Gold)のための関数型プログラミングまとめ - 前編

Javaの資格(OCJP Gold)取得シリーズ関数型プログラミング編です。
mrdshinse.hatenablog.com 前編はJava関数型プログラミングをする前提知識として、関数型インターフェース、ラムダ式、組み込みの関数型インターフェースを説明します。
後編では、Streamクラスとメソッド、メソッド参照、Optionクラスを説明する予定です。

関数型インターフェースを理解する

Java関数型プログラミングを理解するには、関数型インターフェースを理解する必要があります。

関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースのことです。
例)

interface Hello {
  void say(String name);
}

以下の2つは抽象メソッドではないので、定義してあってもOKです。

  • デフォルトメソッド
  • Objectクラスのメソッドのオーバーライド

よって、以下の例も関数型インターフェースです。

@FunctionalInterface
interface Hello {
    void sayHi(String name);
    default void bye(){ System.out.println("bye.");}
    @Override String toString();
}

上記の定義に当てはまるインターフェースであれば、Java7までに作られたインターフェースも関数型インターフェースになっています。 (一部抜粋)

ここまで頭に入れたら、次はラムダ式を書いてみましょう。

TIPS

自分で関数型インターフェースを定義する場合は、@FunctionalInterfaceがなくても関数型インターフェースですが、@FunctionalInterfaceをつけることで抽象メソッドを2つ以上定義したときにコンパイルエラーが出るようになるので、付けておいた方がいいです。

ラムダ式の記法を覚える

ラムダ式とは関数型インターフェースを実装するための記法です。

文法は
 引数 -> 処理
となっています。

例えば、
s -> s.length()
こんな感じ。

書き方のバリエーションが多くてややこしいラムダですが、何が起きているかを理解することが近道です 。

冒頭にも書きましたが、
ラムダ式とは関数型インターフェースを実装するための記法です。
(大事だから2回書いたよ) つまり、ラムダ式の実態は関数型インターフェースを実装した無名クラスです。

コードを見てみます。以下の2つは全く同じ処理となります。

//Java7までの書き方でComparatorを実装
private Comparator c1 = new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        return o1.hashCode() - o2.hashCode();
    }
};
//ラムダ式でComparatorを実装
private Comparator c2 = (Object o1, Object o2) -> {
    return o1.hashCode() - o2.hashCode();
};

更にラムだ式だとここまで省略できます。

private Comparator c3 = (o1, o2) -> o1.hashCode() - o2.hashCode();

だいぶスッキリしましたね。
java.util.Comparatorはcompareメソッドを唯一の抽象メソッドとする関数型インターフェースです。
なので、new Comparator()とか、@Overrideとか、public int compareとか書かなくても推測できます。
であれば、省略して書いた方がいいよね?って要求に応えるのがラムダ式です。

繰り返しですが、文法は
 引数 -> 処理
です。

引数部分は

  • 型指定は任意
  • 型指定するなら全ての引数を型指定する
  • 引数なしの場合は()を書く
  • 引数2個以上の場合は()を書く

処理部分は

  • 式が2つ以上になる場合は、{}をつける。
  • returnで戻り値を指定する場合は、{}をつける。
  • {}をつける場合は文末に;をつける。

等のルールがあります。

{}をつける条件が曖昧なので、どなたかご存知でしたらご教授いただきたいところ。。。

ちなみに、ラムダ式の処理部分から参照できる変数には制限があり、以下の通りです。

ラムダ式から操作できるもの

  • static変数
  • インスタンス変数
  • 事実上finalなメソッド引数
  • 事実上finalなローカル変数

“事実上finalな"とは、初期化されたら代入がされないという意味なので、

操作できないもの

  • メソッド内で代入されるメソッド引数
  • 2度以上代入されるローカル変数

となります。

ラムダ式が書けるようになってきたら、いよいよ関数型プログラミング用のAPIを覚えましょう。

組み込み関数型インターフェースのAPIを覚える

ざっくり言えば関数とは「引数を受け取って戻り値を返す処理」ですが、この「処理」にも引数の数や戻り値の有り無しによってバリエーションがあります。
このバリエーションを表現するために、java.util.functionパッケージには組み込み関数型インターフェースが定義されています。
この章では以下のインターフェースとメソッドを覚えてください。

まずは引数1つパターン。

インターフェース メソッド 戻り値 引数
Supplier T get () なんでも なし
Consumer void accept (T t) なし 1つ
Predicate boolean test (T t) 真偽値 1つ
Function<T, R> R apply(T t) 引数と違う型 1つ
UnaryOperator T apply(T t) 引数と同じ型 1つ

続いて引数2つパターン。

インターフェース メソッド 戻り値 引数
BiConsumer<T, U> void accept (T t, U u) なし 2つ
BiPredicate<T, U> boolean test (T t, U u) 真偽値 2つ
BinaryOperator T apply(T t1, T t2) 引数と同じ型 2つ

java.util.functionパッケージには他にも色々ありますが、OCJP GOld対策であれば上記で十分です。

詳しい使い方は後編で説明するとして、ここでは上記のインターフェースをラムダ式で実装するサンプルを載せてオシマイにします。

//文字列"text"を返すSupplier
private Supplier<String> supplier = () -> {return "text";};
//引数の文字列を標準出力するConsumer
private Consumer<String> consumer = s -> System.out.println(s);
//引数の数値が10以上かどうか判定するPredictor
private Predicate<Integer> predicate = i -> i>=10;
//引数の文字列の文字数を返すFunction
private Function<String, Integer> function = s -> {return s.length();};
//引数の文字列に"***"を付けて返すUnaryOperator
private UnaryOperator<String> uOperator = s -> {return "***"+s+"***";};

参考

関数型プログラミングって何、ラムダってなんだよ http://qiita.com/lrf141/items/98ffbeaee42d30cca4dc

日本語だとJava8 Goldの試験対策本はないので、これが一番かな

英語だけどとても丁寧なテキストはこちら

追記
どうやらJava8 Goldの試験対策本でてたらしいので追加