LoginSignup
32
40

More than 5 years have passed since last update.

ラムダ式で3つ以上の引数に対応しよう

Posted at

引数が2つ以下の場合

Java8既存のFunctionインターフェースを使えば、うまく対応できます。
例:

// 引数が1つしかない
Function<String, String> func1 = (str1) -> str1+"!";
System.out.println(func1.apply("Aha")); //Aha!

// 引数が2つしかない
BiFunction<String, String, String> combineStrings = (str1, str2) -> str1+str2;
System.out.println(combineStrings.apply("Hello ", "World")); //Hello World

なお、既存のFunctionインターフェースを分かりやすくまとめてくれた図表を見つけましたので、転載します

Java 8 Functional Interface Naming Guide

引数が3つ以上の場合

解決法1: 自作関数型インターフェース

Java8のFunctionインターフェースにはインプットが3つ以上のインターフェースが提供されていません。
ならば、Functionインターフェースを自作してみましょう。
実は、ありがたいことに、Java8がただ単純にインプットが3つ以上の関数型インターフェースを提供してないだけで、(少なくてもOracleとOpenJDKのJavaでは)複数の引数へ対応する準備が整っています。どういうことでしょうか?まず、BiFunctionのソースを見てみましょう。


@FunctionalInterface
public interface BiFunction<T, U, R> {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     */
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

肝心なのはR apply(T t, U u);だけです。このapply関数がjava.lang.reflect.Proxy.KeyFactory#applyで実装されています。

    private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // the most frequent
                case 2: return new Key2(interfaces[0], interfaces[1]);
                case 0: return key0;
                default: return new KeyX(interfaces);
            }
        }
    }

ご覧のように、KeyXが存在していて、その中でfor文を使って、interfaceごとに読み込み、レファレンスを作っています。なので、自作関数型インターフェース内でFunctionやBiFunctionと似ている感じでapplyを使えば良いでしょう。

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v); // KeyX should be called
}

そして、このTriFunctionを使って、3つ以上の引数が存在するケースに対応すると、

TriFunction<String, Integer, Integer, String> newSubString = (str, start, number) -> 
  str.substring(start-1, str.length() <= start-1+number? str.length(): start-1+number);

String f = newSubString.apply("Hello World", 2, 7);
System.out.println(f); // ello Wo

うーん、悪くありません。しかし、もし単純な処理を行いたいだけであれば、わざわざインターフェースを作るなんか少し面倒臭いでしょう。他に方法ないでしょうか。

解決法2: カリー化

JavaScriptの経験者であれば、既に思い付いたかもしれません。カリー化すればいいです。では、カリー化とは何でしょうか?

カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。

Wikipediaより

要するに、Java 8では、関数が変数のように扱うことができますので、戻り値で関数を返す手法が利用できます。

Function<String, Function<Integer, Function<Integer, String>>> newSubString = (str) -> (start) -> (number) 
  -> str.substring(start-1, str.length() <= start-1+number? str.length(): start-1+number);


String f = newSubString.apply("Hello World").apply(2).apply(7);
System.out.println(f); // ello Wo

一回目、二回目では関数が返され、そして実行スコップから出ていない為、前の変数がクロージャーで持っていて、次の処理でも使用されます。
こんな感じで既存インターフェースを使うだけで、複数の引数へ対応できるようになりました。

32
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
40