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の試験対策本でてたらしいので追加