Java8 - 関数型インタフェース (Functional Interface) について

関数型インタフェースとは、1 つの抽象メソッドを持つインタフェースを指します。 Single Abstract Method(SAM)とも呼ばれます。

たとえば、以下のようなインタフェースを関数型インタフェースといいます。

public interface FunctionalInterface {
    public abstract void doSomething(String text);
}

この記事では、関数型インタフェースの基本的な使用方法と例について説明します。

1. 関数型インタフェースを使用する理由

関数型インタフェースを使用する理由は、Javaのラムダ式は関数型インタフェースでしかアクセスできないからです。

たとえば、次のコードでは、変数funcはラムダ式で生成されたオブジェクトを指しています。 doSomething()に引数として文字列を渡すと、ラムダ式で定義されているようにログに出力されます。

public interface FunctionalInterface {
     public abstract void doSomething(String text);
}

FunctionalInterface func = text -> System.out.println(text);
func.doSomething("do something");

Output:

do something

これまで開発をしながら匿名クラスでオブジェクトを作成したことがありました。 以下のコードは、匿名クラスを使用してリファクタリングしたコードです。関数型インタフェースとラムダ式で匿名クラシックを簡単に表現したと考えられます。

FunctionalInterface func = new FunctionalInterface() {
    @Override
    public void doSomething(String text) {
        System.out.println(text);
    }
};
func.doSomething("do something");

まとめると、関数型インタフェースを使用することはラムダ式で作成されたオブジェクトにアクセスするためです。 上記の例のように、ラムダ式を使用するたびに関数型インタフェースを毎回定義するのは不便であるため、Javaからライブラリとして提供するものがあります。

2. 基本関数型インタフェース

Javaがデフォルトで提供する関数型インタフェースには、次のものがあります。

  • Runnable
  • Supplier
  • Consumer
  • Function<T, R>
  • Predicate

これ以外にも様々なものがあります。 Javaのjava.util.functionパッケージで定義されています。ご希望の場合はこちらをご確認ください。

さて、例で代表的な関数型インタフェースをどのように活用できるかを見てみましょう。

3. Runnable

Runnable は引数を受けず、戻り値もないインタフェースです。

public interface Runnable {
  public abstract void run();
}

以下のコードのように使用できます。

Runnable runnable = () -> System.out.println("run anything!");
runnable.run();

Output:

run anything!

Runnable は run() を呼び出す必要があります。関数型インタフェースごとに run() のような実行メソッド名が異なります。 インタフェースの種類ごとに作成された目的が異なり、その目的に合った名前を実行メソッド名で決めたからです。

4. Supplier

Supplier<T>は引数を受けずにT型のオブジェクトを返します。

public interface Supplier<T> {
    T get();
}

以下のコードのように使用できます。 get() メソッドを呼び出す必要があります。

Supplier<String> getString = () -> "Happy new year!";
String str = getString.get();
System.out.println(str);

Output:

Happy new year!

5. Consumer

Consumer<T>はT型のオブジェクトを引数として受け取り、戻り値はありません。

public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

以下のコードのように使用できます。 accept()メソッドを使用します。

Consumer<String> printString = text -> System.out.println("Miss " + text + "?");
printString.accept("me");

Output:

Miss me?

また、 andThen() を使用すると、2 つ以上の Consumer を連続して実行できます。

Consumer<String> printString = text -> System.out.println("Miss " + text + "?");
Consumer<String> printString2 = text -> System.out.println("--> Yes");
printString.andThen(printString2).accept("me");

Output:

Miss me?
--> Yes

6. Function

Function<T, R>はT型の引数を受け取り、R型のオブジェクトを返します。

public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

次のように使用できます。 apply()メソッドを使用します。

Function<Integer, Integer> multiply = (value) -> value * 2;
Integer result = multiply.apply(3);
System.out.println(result);

Output:

6

compose() は、2 つの Function を組み合わせて新しい Function オブジェクトを作成するメソッドです。 注意すべき点は andThen() とは実行順序が逆です。 compose() に引数として渡される Function が最初に実行され、その後に呼び出されるオブジェクトの Function が実行されます。

たとえば、次のように compose を使用して新しい Function を作成できます。 apply を呼び出すと add が最初に実行され、その後 multiply が実行されます。

Function<Integer, Integer> multiply = (value) -> value * 2;
Function<Integer, Integer> add      = (value) -> value + 3;

Function<Integer, Integer> addThenMultiply = multiply.compose(add);

Integer result1 = addThenMultiply.apply(3);
System.out.println(result1);

Output:

12

7. Predicate

Predicate<T>はT型引数を受け取り、結果としてbooleanを返します。

public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

次のように使用できます。 test() メソッドを使用します。

Predicate<Integer> isBiggerThanFive = num -> num > 5;
System.out.println("10 is bigger than 5? -> " + isBiggerThanFive.test(10));

Output:

10 is bigger than 5? -> true

and()or() は他の Predicate と一緒に使われます。 直観的に and() は 2 つの Predicate が true のとき true を返し、 or() は 2 つのうち 1 つだけ true なら true を返します。

Predicate<Integer> isBiggerThanFive = num -> num > 5;
Predicate<Integer> isLowerThanSix = num -> num < 6;
System.out.println(isBiggerThanFive.and(isLowerThanSix).test(10));
System.out.println(isBiggerThanFive.or(isLowerThanSix).test(10));

Output:

false
true

isEqual() は static メソッドで、引数として渡されるオブジェクトと同じかどうかをチェックする Predicate オブジェクトを作成します。 次のように使用できます。

Predicate<String> isEquals = Predicate.isEqual("Google");
isEquals.test("Google");

Output:

true

8. References

Related Posts

codechachaCopyright ©2019 codechacha