lambdaについて調べてみた その3

【概要】

前回まではパラメータ一つの場合について調べてみました。

っで、複数のパラメータを受け取る場合にはどうすればいいのか
というのについて調べてみます。

【パラメータ2つ】

まずはパラメータ2つの場合、どうすればいいのかです。

    final BiConsumer<String, String> biConsumer = (left, right) -> System.out.println(left + "-" + right);
    final BiFunction<String, String, Integer> biFunction = (left, right) -> (left + "-" + right).length();

    biConsumer.accept("1234", "567");
    System.out.println(biFunction.apply("1234", "567"));

パラメータが二つの場合、BiConsumerとBiFunctionを使います。
それぞれ頭にBiをくっつけただけで、使い方は変わりません。

パラメータが2つの場合は
パラメータの型定義部分の括弧をつける必要があります。

【パラメータ3つ以上】

パラメータ2つの場合は標準のAPIで用意されていますが、
3つ以上となると自分で作成する必要があります。

まずはインターフェイスを作成します。

@FunctionalInterface
public interface SampleConsumer<T, TT, TTT> {

    void acceptSample(T t, TT tt, TTT ttt);
}

@FunctionalInterface
public interface SampleFunctional<T, TT, TTT, R> {
    R applySample(T t, TT tt, TTT ttt);
}

publicじゃなくても良いです。
サンプルなので細かいところは無視してください。

サンプルではTで始まっているのがパラメータの型、Rが戻り値の型として定義しています。

FunctionalInterfaceアノテーションですが、
ラムダとして使える関数型インターフェイスになっているかをチェックするアノテーションです。

アノテーションをつけなくても動きますが、
つけておいたほうがコンパイルでチェックしてくれるのでつけたほうが良いと思います。

    final SampleConsumer<String, String, String> sampleConsumer =
            (left, center, right) ->
                    System.out.println(left + "-" + center + "-" + right);

    final SampleFunctional<String, String, String, Integer> sampleFunctional =
            (left, center, right) ->
                    (left + "-" + center + "-" + right).length();

    sampleConsumer.acceptSample("123", "456", "789");
    System.out.println(sampleFunctional.applySample("123", "456", "789"));

使い方も特に2つの場合と変わりません。
メソッド名は自分で定義したものを使っています。

【パラメータ3つ以上できるけど・・・】

パラメータは1つか2つまでにした方が
コードが見やすくて良いと個人的には思います。

では、先ほどのサンプルを直してみましょう。

[パラメータクラス]

まずはパラメータ用のクラスを作成します。

public class SampleParam {
    public final String left;
    public final String center;
    public final String right;

    public SampleParam(final String left, final String center, final String right) {
        this.left = left;
        this.center = center;
        this.right = right;
    }

    public void outprint() {
        System.out.println(left + "-" + center + "-" + right);
    }

    public Integer length() {
        return (left + "-" + center + "-" + right).length();
    }
}

クラスを作成したファイルを別にしたくない場合は
privateクラスなりで内部だけで使うで良いと思います。

[書き換えてみる]

と言うことで元のコードを書き換えてみます。

    final Consumer<SampleParam> sampleParamConsumer =
            param -> System.out.println(param.left + "-" + param.center + "-" + param.right);

    final Function<SampleParam, Integer> sampleParamFunction =
            param -> (param.left + "-" + param.center + "-" + param.right).length();

    final SampleParam sampleParam = new SampleParam("123", "456", "789");
    sampleParamConsumer.accept(sampleParam);
    System.out.println(sampleParamFunction.apply(sampleParam));

とりあえず、括弧をはずすことができました。
これだけだとあんまりメリットは感じません。

続けてメソッド参照に変えてみます。

    final Consumer<SampleParam> sampleParamConsumer2 = SampleParam::outprint;
    final Function<SampleParam, Integer> sampleParamFunction2 = SampleParam::length;

    final SampleParam sampleParam = new SampleParam("123", "456", "789");
    sampleParamConsumer2.accept(sampleParam);
    System.out.println(sampleParamFunction2.apply(sampleParam));

メソッド参照のみになりました。

必要なプロパティとそれを処理するメソッドを持った
クラスを作って、それをパラメータに渡すと非常にシンプルなコードが作れます。
ここらへんはオブジェクト指向の考え方の基本ですね。

クラスを新たに作る必要があるのと
StreamAPIなどで内部で使っている時に一目では処理がわからなくなる
というデメリットはあります。

逆にコードがシンプルになるのと
クラスを作成しているのでユニットテストしやすいメリットはあると思います。

【まとめ】

複数のパラメータを取り扱う時は
2つまではBiConsumere、BiFunctionを使えばよいです。

3つ以上になると自作する必要がありますが、
極力2つ以内に押さえたほうが個人的には良いと思います。