【Java】Java8のあれこれ③ 関数型インターフェース(1)

はじめに

拝啓、故郷のお母さん。

3回目のブログです。
なんとか3回分のブログを書くことができています。

最近、ブログ執筆を強要してきた指示なされた上司の週報にブログ執筆者を気遣うお言葉を載せているのを見て、上司もとうとう心を入れ替えたかとほだされかけたが、俺は騙されないぞ大層ご心配なさっているご様子なので、今回も一生懸命書き上げたいと思います。

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

敬具


挨拶

こんにちは、人を疑うことの知らないzenpocoponです。
あまりに人を疑わな過ぎてそんな自分自身を疑う毎日です。


今回のテーマ

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

関数型インターフェース

java8(jdk8)から追加された新機能の一つです。

関数型インターフェースは、名前にもあるようにJavaの基本機能であるインターフェースを応用した機能の一つで、抽象メソッドを一つだけもつ(Single-Abstract-Method Type:SAM Type)インターフェースのことを指します。

関数インターフェースの定義

以下のようなインターフェースがあるとします。

public Interface ExampleInterface {

// 足し算の抽象メソッド
public int add(int i, int j);

}

一見ごく普通のインターフェースですが、定義されている抽象メソッドが一つだけなので、この場合は関数型インターフェースとして判断されます。そして、こうして定義した関数型インターフェースが、Java8の追加機能の肝である関数を作成するための型になります。

関数の作成

関数を実装する際は、前回解説したラムダ式使います。
先ほど定義した関数型インターフェースを型に持つ関数exFunctionを実装してみます。

public class ExampleClass {
public void exampleMethod() {

ExampleInterface exFunction = (int i, int j) -> i + j;

}
}

これで、足し算を行う関数exFunctionができました。

この関数を使用する際は、ExampleInterfaceで定義されている抽象メソッドadd(int i, int j)を呼び出します。

System.out.println(exFunction.add(5, 6)); // 実行結果 11

ラムダ式の仮引数(int i, int j)は、関数型インターフェースに定義している抽象メソッドの引数と合わせる必要があります。
また、抽象メソッドで戻り値の型が指定されていた場合は、ラムダ式でもその型に合った型を返却しなければいけません。

関数は、変数と同じようにクラスのメンバとしても定義できますし、メソッド内部で定義することができます。また、スコープも変数と同様です。

public class ExampleClass {

// メンバ関数
public ExampleInterface exFunction1 = (int i, int j) -> i + j;

public ExampleMethod1() {
// ローカル関数
ExampleInterface exFunction2 = (int i, int j) -> i + j;

exFunction1.add(2, 3); // 実行結果 5
exFunction2.add(10, 15); // 実行結果 25
}

public ExampleMethod2() {
exFunction1.add(2, 3); // 実行結果 5
exFunction2.add(10, 15); // スコープ外なのでコンパイルエラー
}
}

メンバとして定義した関数は、定義したクラス内のメソッドで使用することができ、アクセス修飾子をpublicにすることで定義したクラス外でも使用することができます。
逆に、メソッド内部でローカル関数として定義した場合は、そのメソッド内部でしか使用できません。

正確ではないかもしれませんが、変数は「値(またはその参照)」を持つのに対して、関数は「処理(の参照)」を持つと考えるとイメージしやすいかもしれません。

関数型インターフェースの制約

関数型インターフェースにはいくつかの制約があります。
以下のように何かしらの抽象メソッドを一つでも追加してしまうと関数型インターフェースとして扱うことができません。

public Interface ExampleInterface2 {

// 足し算の抽象メソッド
public int add(int i, int j);

// 掛け算の抽象メソッド
public int mul(int i, int j);

}

例外として、メソッドはメソッドでもデフォルトメソッドstaticメソッドと呼ばれるものは抽象メソッドとしてカウントされないため、同じインターフェース内で複数定義されていても問題ありません。
デフォルトメソッドやstaticメソッドはJava8から追加された新しい機能で、上記のようにインターフェース内に実装を持つことができます。詳しい説明はまた別のエントリーで説明します。

public Interface ExampleInterface3 {

// 足し算の抽象メソッド
public int add(int i, int j);

// 引き算のデフォルトメソッド
public default int sub(int i, int j) {
return i - j;
}

// 掛け算のstaticメソッド
public static int mul(int i, int j) {
return i * j;
}

また、Objectクラスのpublicメソッド(toString()equals()など)も抽象メソッドとしてはカウントされないので、同一インターフェース内に複数存在していても抽象メソッドが一つだけあれば関数型インターフェースとして扱われます。

FunctionalInterfaceアノテーション

関数型インターフェースの定義は単純なものですが、その分間違いも発生しやすくなります。
ついうっかり抽象メソッドを追加してしまったり、逆に一つしかない抽象メソッドを削除しまうなど、特にチームで開発をしている場合は、相互の情報共有が十分でないとうっかりミスが起こりがちです。

そんなうっかりを防ぐための機能として、FunctionalInterfaceアノテーションが用意されています。
使い方はクラス名の直前に記述するだけです。

@FunctionalInterface
public interface ExampleInterface4 {
// 何かしらの記述
}

既存の機能であるOverrideアノテーションが正しくオーバーライドできているかどうかをチェックするのと同じように、FunctionalInterfaceアノテーションを付与することで、関数型インターフェースの制約に違反しているとコンパイルエラーが発生し、うっかりミスを未然に防ぐことができます。
人生にもこんなアノテーションがあったらいいですね!

まとめ

今回は関数型インターフェースと、関数の作り方について解説しました。

いくつかの制約があるとしても、FunctionalInterfaceアノテーションもあるので定義の仕方自体は難しい話ではないかと思います。

でも、今日のエントリーを見て「定義自体は特に難しいことはないが、単純な処理を作るだけでもわざわざ関数型インターフェースを作らなければいけないのはちょっと利便性に欠けるような・・・」と思った方、安心してください。
Java8では標準関数型インターフェースという便利な機能が用意してあります。


次回予告

という事で、次回は標準関数型インターフェースについてです。

本当は関数型インターフェースのテーマとして1つにまとめるつもりでしたが、文字数が多くなってしまってわかりにくいかなと思ったので、今回はここで切ります。
断じてこれ以上書くのが面倒とか、尺稼ぎとかではないですよ、ええ、ええ。


おわりに

最近英語の勉強を始めたのですが、知人の前で発音の練習をしていると「なんか外国かぶれみたいでくっそ腹立つ」と言われました。
相手をイラつかせたい人は是非実践してみてください。(無駄な処世術の勧め)

コメントを残す

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

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