Java8のあれこれ③ 関数型インターフェース(2)

はじめに

拝啓、故郷のお母さん。

ブログの執筆にも慣れてきました。
そこで、これまで書いてきたブログを見返したのですが、これってブログなのか疑問に感じるようになりました。
ブログってどんなこと書けばいいんでしたっけ・・・。

とりあえず、今回も一生懸命書き上げます。

どうか、遠い北の大地で見守っていてください。お体にはお気をつけて。

敬具


挨拶

こんにちは、道に迷いがちなzenpocoponです。
人生という道にもよく迷います。というベタベタなコメントは嫌いです。


今回のテーマ

今回のテーマは、前回に引き続き【 関数型インターフェース 】についてです。

前回は自作の関数型インターフェースについて、「作り方自体は単純だぜ!」という説明をしましたが、単純とはいえ毎度毎度実装に合わせて用意するのはなかなか手間がかかります。
そんなときのために(かどうかはわかりませんが)、標準関数型インターフェースという、汎用的に使えるJava標準の関数型インターフェースが用意されています。

標準関数型インターフェース

Javaに標準で実装されている関数型インターフェースです。
基本的な使い方は自作関数型インターフェースと同じですが、引数や戻り値の型を型引数で指定することができ、どんな型でも汎用的に扱うことができます。
Javadoc(java.util.function (Java Platform SE 8))を覗いてみると結構な数が用意されていて一瞬覚える気を失いますが、以下の基本的なものさえ覚えておけばある程度パターン化可能です。

  • Function … 引数が1つで戻り値がある
  • Consumer … 引数がなくて戻り値がある
  • Supplier … 引数が1つで戻り値がない
  • Predicate … 引数が1つで戻り値が必ずboolean型

早速それぞれを解説していきます。

Function

引数を一つとって戻り値を返す関数型インターフェースです。

// 数値(センチメートル)を引数に渡すと、メートルに変換して単位を付与した文字列を戻す関数
Function<Integer, String> function = (Integer cm) -> {
return Integer.toString(cm / 100) + "メートル";
};

1つ目の型引数は仮引数の型で、2つ目の型引数が戻り値の型です。
上記の例では、Integer型の引数をとってString型の文字列を返しています。

定義した関数は以下のように使用します。

System.out.println(function.apply(600));
// 実行結果 6メートル

Consumer

引数を1つとって、戻り値のない関数型インターフェースです。

// 名前を引数に渡すと、自己紹介を標準出力する関数。
Consumer consumer = (String name) -> {
System.out.println("私の名前は" + name + "です。");
};

型引数には仮引数の型を指定します。
戻り値はなく、関数の中で処理が完結します。

定義した関数は以下のように使用します。

consumer.accept("zenpocopon");
// 実行結果 私の名前はzenpocoponです。

Supplier

引数をとらず、戻り値のみ返す関数型インターフェースです。

// らーめんの味のListを返す関数
Supplier supplier = () -> {
return Arrays.asList("みそ", "しょうゆ", "しお");
};

Consumerとは逆に、型引数には戻り値の型を指定します。

定義した関数は以下のように使用します。

for (String aji : supplier.get()) {
System.out.print(aji + "ラーメン ");
}
// 実行結果 みそラーメン しょうゆラーメン しおラーメン

Predicate

引数を一つとり、boolean型の引数を返す関数型インターフェースです。

// 数値(年齢)を渡すと30歳以上かどうかの判定を返す関数
Predicate predicate = (Integer age) -> {
return age >= 30;
};

型引数には仮引数の型を指定します。
戻り値もありますが、必ずboolean型が返されるため、型引数の指定はありません。

定義した関数は以下のように使用します。

System.out.println(predicate.test(31) ? "もうおじさん" : "まだ若い");
// 実行結果 もうおじさん

以上が標準関数型インターフェースの基本的な形です。
残りはこれらの使い回しのようなものなので、次にざっくりと説明していきます。

その他の標準関数型インターフェース

おおまかに以下のように分類できます。
– 引数が複数ある
– プリミティブ型を扱う
– プリミティブ型を引数にとる
– プリミティブ型を返す
– プリミティブ型の引数から別のプリミティブ型の戻り値を返す
– オブジェクト型とプリミティブ型を引数に取る
– その他

それぞれ、先述の基本的な関数に対してある程度決まったパターンを適用することで表現することができるので、基本的なものさえ覚えておけば、無理して覚えなくてもなんとなーく使えると思います。

引数が複数ある

基本形となる関数型インターフェース名の頭に「Bi」を付与します。
※Operator系のBinaryOperatorだけは例外(後述)
例 : BiFunction、BiConsumer

プリミティブ型を扱う

Javaの言語仕様的に、プリミティブ型を参照型と同じように扱うのが難しい(ジェネリクスでプリミティブ型が使えないことから派生する諸問題)ようで、int型long型double型を扱うための関数型インターフェースが別に用意されています。

プリミティブ型を引数にとる

基本形となる関数型インターフェース名の頭に、対象のプリミティブ型を付与します。
引数はインターフェース名の頭に付与したプリミティブ型になるので、型引数では指定しません。
例 : IntFunction、LongConsumer

プリミティブ型を返す

基本形となる関数型インターフェース名の頭に、対象のプリミティブ型を付与します。
戻り値の型は付与する名称で決まるので、プリミティブ型を引数にとる場合と同様に、型引数で戻り値の型を指定はできません。
例 : ToIntFunction、DoubleSupplier

プリミティブ型の引数から別のプリミティブ型の戻り値を返す

名称のつけ方は他のパターンと同様です。
このパターンは引数と戻り値の型を指定する必要のあるFunction型のみで、これも型引数は指定しません。
例 : IntToLongFunction、DoubleToIntFunction

オブジェクト型とプリミティブ型を引数に取る

任意の型とプリミティブ型を引数に取るパターンです。
このパターンはConsumer型のみで、変換対象の型を型引数として指定します。
例 : ObjIntConsumer、ObjLongConsumer

その他

上記のどれにも属さないパターンです。

Runnable

引数もなく、戻り値もない関数型インターフェースです。
実装した関数の中で閉じた処理ができます。

// "Hello world!"を標準出力する関数
Runnable runnable = () -> {
System.out.println("Hello world!");
};

定義した関数は以下のように使用します。

runnable.run();
// 実行結果 Hello world!

そのままなので、特筆することはありません。

Operator系

引数も戻り値もありますが、引数で指定した型が返される関数型インターフェースです。
実際はOperator型というインターフェースはなく、引数の数で名称が変わります。
以下で例を挙げる UnaryOperatorは引数が一つのパターンです。

// 引数で渡された数値を2倍にして返す関数
UnaryOperator unaryOperator = (Integer i) -> {
return i * 2;
};

型引数には引数と戻り値の型を指定しています。Function型で型引数を2つとも同じ型を指定した時と同じ動きになります。

定義した関数は以下のように使用します。

int i = 5;
System.out.println(i + "を2倍にすると" + unaryOperator.apply(i) + "です。");
// 実行結果 5を2倍にすると10です。
}

次に、引数が2つあるBinaryOperatorです。

// 二つの数値を足し算する関数
BinaryOperator binaryOperator = (i, j) -> {
return i + j
}

一つ目の引数、二つ目の引数、戻り値のすべてが型引数で指定した型になります。

定義した関数は以下のように使用します。

int i = 5;
int j = 7;
System.out.println(i + " + " + j + " = " + binaryOperator.apply(i, j));
// 実行結果 5 + 7 = 12

個人的な感想

無駄に多すぎるよ。。
この融通の利かない具合が実にJavaっぽいですね。
そこにしびれ(ry

まとめ

今回は標準関数型インターフェースについて解説しました。

無駄にインターフェースを作成することもないですし、Javaの標準に合わせることは可読性の向上にもつながるため、これといった制約がない場合は標準関数型インターフェースを使用した方がよいでしょう。
また、先述した4つの基本的なパターン(Function, Consumer, Supplier, Predicate)はStreamAPIを使う際にも度々登場するので、しっかり押さえておきたいですね。


次回予告

次回はStreamAPIについてです。
Javaにおける繰り返し処理がまた進化します。


おわりに

先日、レンタカーで長時間運転したんですが、翌々日くらいに腕が筋肉痛になっていました。
ええ、せいぜいハンドルを握るくらいしかしていないのに、腕が。

如何に楽をするかを追求する現代文明に対して勝利したといっても過言ではないでしょう。(過言)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>