Java8 - 関数型インターフェース(Functional Interface)まとめ

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

たとえば、次のようなインタフェースを関数型インターフェースと呼ばれます。

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

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

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

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

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

FunctionalInterface func = text -> System.out.println(text);
func.doSomething("do something");
// 実行結果
// do something

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

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

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

基本的な関数型インターフェース

Javaで基本的に提供される関数型インターフェースは、次のようなものがあります。

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

この他にも様々なものがあります。 ジャワのjava.util.functionパッケージで定義されているので、より多くのことを確認して方はこちらをご確認ください。

Runnable

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

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

次のコードのように使用することができます。

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

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

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);
// 結果
// Happy new year!

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");
// 結果
// Miss me?

また、 andThen()を使用すると、二つ以上のConsumerを連​​続的に実行することができます。

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

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);
// 結果
// 6

compose()は二つの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);
// 結果
// 12

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));
// 結果
// 10 is bigger than 5? -> true

and()or()は、他のPredicateと一緒に使用されます。 直感的に and()は二つのPredicateがtrueの場合、trueを返して or()は二つの中だけ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));
// 結果
// false
// true

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

Predicate<String> isEquals = Predicate.isEqual("Google");
isEquals.test("Google");
// 結果
// true

まとめ

Javaで基本的に提供される関数型インターフェースについて調べてみました。インターフェースごとに引数と戻り値の型が異なり、名前が異なります。 使用する目的に合わせて名付けました。そのため、実行するメソッドの名前も異なります。

参考

codechachaCopyright ©2019 codechacha