1. はじめに
関数型インターフェースとは、実装を持たない抽象メソッドをちょうど 1 つだけ含むインターフェースのことです。こうしたインターフェースはメソッド実装を簡潔に記述するために使え、Java ではラムダ式(これは後で学びます)を用いて実現します。
なぜメソッドは 1 つだけ?
関数型インターフェースはちょうど 1 つの操作を表すからです。もしメソッドが 2 つ以上あると、どのメソッドを実装すべきかが曖昧になります。したがってルールは簡単です: 1 つのインターフェース = 1 つの抽象メソッド。
標準ライブラリの例
- Runnable — スレッド内のタスク用(void run())
- Callable<V> — 結果を返すタスク用(V call())
- Comparator<T> — オブジェクトの比較用(int compare(T o1, T o2))
- Consumer<T> — 値の「コンシューマ」(void accept(T t))
- Supplier<T> — 値の「サプライヤ」(T get())
- Function<T, R> — T から R への関数(R apply(T t))
- Predicate<T> — 条件判定(boolean test(T t))
たとえば、インターフェース Runnable は次のとおりです:
public interface Runnable {
void run();
}
こちらはインターフェース Comparator の例です:
public interface Comparator<T> {
int compare(T o1, T o2);
// ... default メソッドや static メソッドは他にもあるが、抽象メソッドは 1 つだけ!
}
重要なポイント: default メソッドと static メソッドは抽象メソッドとは見なされないため、いくつあっても構いません。
2. アノテーション @FunctionalInterface
Java は厳格な言語です。混乱を避けるため、アノテーション @FunctionalInterface を使ってインターフェースを関数型として明示的にマークできます。これは「ボタンは 1 つだけで動きます!」という注意ラベルのようなものです—余計なものを足されないようにするためです。
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
この状態で、もしうっかり 2 つ目の抽象メソッドを追加すると、コンパイラはただちにエラーを報告します:
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // エラー!抽象メソッドが 2 つある
}
アノテーションは必須?
いいえ、必須ではありません。抽象メソッドがちょうど 1 つなら、アノテーションがなくてもそのインターフェースは関数型です。ただしアノテーションを付ければ意図を明確に示せるうえ、偶発的なミスから身を守れます。
default や static メソッドを追加してもいい?
はい、可能です!大事なのは、抽象メソッドが 1 つだけであること。他のメソッドは default や static として、好きなだけ追加できます。
例:
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("私はファンシーな操作です!");
}
static void description() {
System.out.println("算術のための関数型インターフェース。");
}
}
3. 宣言と使用例
たとえば 2 つの数に対する操作を表したいとします。次のように書けます:
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
このインターフェースは様々な方法で実装できます。
通常のクラスで実装する場合
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
使用例:
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
匿名クラスで実装する場合
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
ラムダに関する注意
Java 8 以降、この種のインターフェースはラムダ式で手軽に実装できます(より短い構文)。ラムダは数回先の講義で学ぶので、今は「関数型インターフェースはラムダと相性がよく、使い勝手を高めるために用意されている」と理解しておけば十分です。
4. 実践: 自分の関数型インターフェースを書いてみよう
課題 1. 自作の Action を作ろう!
文字列を受け取り、何も返さないインターフェース Action を作成しなさい。文字列を大文字にして印字する匿名クラスで実装しなさい。
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("java を勉強中!"); // JAVA を勉強中!
}
}
(後で、ラムダ式を使ってもっと短く書ける方法を見ていきます。)
課題 2. 数値フィルタ
NumberPredicate インターフェースを作り、メソッド boolean test(int n) を定義しなさい。匿名クラスで偶数判定を実装しなさい。
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
課題 3. 標準インターフェースを使う
自作インターフェースの代わりに、用意済みの Predicate<Integer> を使っても構いません:
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
表: 標準ライブラリの関数型インターフェース
| インターフェース | メソッド | 説明 | 使用例 |
|---|---|---|---|
|
|
引数も戻り値もないタスク | スレッド、タイマー |
|
|
結果を返すタスク | スレッド、ExecutorService |
|
|
2 つのオブジェクトの比較 | コレクションのソート |
|
|
値の「コンシューマ」 | コレクションの反復処理 |
|
|
値の「サプライヤ」 | 遅延初期化、データ生成 |
|
|
T から R への関数 | データ変換 |
|
|
条件判定 | コレクションのフィルタリング |
5. 関数型インターフェースでよくあるミス
エラー1: 2 つ目の抽象メソッドを追加してしまった。 インターフェースに抽象メソッドが 2 つ以上あると、もはや関数型ではありません。コンパイラ(特に @FunctionalInterface が付いている場合)はすぐにエラーを報告します。
エラー2: default や static メソッドは抽象メソッドに数えないことを忘れている。 それらは関数型インターフェースに自由に追加できます—「抽象メソッドは 1 つだけ」というルールには反しません。
エラー3: メソッドシグネチャを誤って実装した。 たとえばインターフェースは 2 つの引数を要求しているのに、1 つだけのメソッドを書いてしまう、など。常にシグネチャを確認しましょう。
エラー4: @FunctionalInterface を付けず、うっかりインターフェースを台無しにしてしまう。 アノテーションを付けないと、余計なメソッドを追加してしまい、なぜコードが動かないのか長時間悩むことになり得ます。意図を明確にするためにも、基本的にはアノテーションを付けるのがおすすめです。
GO TO FULL VERSION