プロジェクト

全般

プロフィール

Javaプログラミング ラムダ式

関数インタフェース

ラムダ式として使用できる関数インタフェースについてのメモです。

Java SE標準関数インタフェース

java.util.functionパッケージとして、標準の関数インタフェースが提供されています。

判定(booleanを返す)

引数を0個、1個、または2個を取り、booleanを返却するメソッドを定義した関数インタフェースです。

  • Predicate<T>
    • boolean test(T t)
  • BiPredicate<T, U>
    • boolean test(T t, U u)
  • BooleanSupplier
    • boolean getAsBoolean()
  • DoublePredicate
    • boolean test(double value)
  • IntPredicate
    • boolean test(int value)
  • LongPredicate
    • boolean test(long value)

処理(戻り値なし)

引数を1個、または2個を取り、返却値はないメソッドを定義した関数インタフェースです。副作用な処理の実装に使います。
引数0個が必要な場合、java.lang.Runnableインタフェース(void run())が関数インタフェースとして使えます。

  • Consumer<T>
    • void accept(T t)
  • BiConsumer<T, U>
    • void accept(T t, U u)
  • DoubleConsumer
    • void accept(double value)
  • IntConsumer
    • void accept(int value)
  • LongConsumer
    • void accept(long value)
  • ObjectDoubleConsumer<T>
    • void accept(T t, double value)
  • ObjectIntConsumer<T>
    • void accept(T t, int value)
  • ObjectLongConsumer<T>
    • void accept(T t, long value)

処理(戻り値あり)

入力(引数)と出力(戻り値)のある関数処理に使うメソッドを定義した関数インタフェースです。

  • Function<T, R>
    • R apply(T t)
  • BiFunction<T, U, R>
    • R apply(T t, U u)

T.B.D.

メソッド設計をラムダ式で洗練

範囲を扱うメソッドの設計とラムダ式

はてな日記に以前、範囲を扱う という題名で範囲チェックを行うライブラリを検討しました。
そのときの題材をラムダ式で洗練されてみようと思います。

メソッドの引数にフラグが多数登場して不恰好になるものはラムダですっきりさせると

まず、以前の日記で書いた、引数に数値の範囲を取り、範囲内外の判定をするクラスRangeの実装イメージを再掲します。
範囲は上限値、下限値で指定し、また、以上、以下、より大きい、未満を扱えるものとします。

public class Range<T extends Comparable> {
    T lowerBound;              // 下限値
    boolean withLowerBound;    // 下限値を範囲に含むか
    T upperBound;              // 上限値
    boolean withUpperBound;    // 上限値を範囲に含むか
    public Range(T lowerBound, boolean withLowerBound, T uppderBound, boolean withUpperBound) {
        this.lowerBound = lowerBound;
        this.withLowerBound = withLowerBound;
        this.upperBound = upperBound;
        this.withUpperBound = withUpperBound;
    }
    public boolean includes(T value) {
        return (withLowerBound ? lowerBound.compareTo(value) <= 0 : lowerBound.compareTo(value) < 0) &&
               (withUpperBound ? value.compareTo(upperBound) <= 0 : value.compareTo(upperBound) < 0);
    }
}

この範囲クラスを利用するコードは

Range<Integer> speedRange = new Range<>(0, true, 180, true);

と、引数に範囲の境界値とフラグが並んで不恰好(可読性劣化)なものになります。ぱっと見てコードが理解できず、Rangeクラスのコンストラクタの引数の説明をJavadoc等で調べてやっと理解ができます。

このRangeクラスをラムダ式を使って書くと

public class Range<T extends Comparable> {
    Predicate<T> judge;
    public Range(Predicate<T> judge) {
        this.judge = Objects.requireNonNull(judge);
    }
    public boolean includes(T value) {
        return judge.test(value);
    }
}

となります。コンストラクタの引数は関数インタフェース(@FunctionalInterface)のjava.util.function.Predicate<T>型で、実装すべき唯一のメソッドboolean test(T v) を持ちます。

このラムダ式版範囲クラスを利用するコードは

Range<Integer> speedRange = new Range<>(v -> 0 <= v && v <= 180);

と、Predicate型のtestメソッド(引数T:ここではInteger、戻り値boolean)をラムダ式で記述したものをRangeコンストラクタに渡しています。ラムダ式では範囲判定がそのまま見えるので、ぱっと見てコードが理解できます。

また、非ラムダ式版範囲クラスでは、飛び飛びの値を有効とする検査はできませんが、ラムダ式版なら簡単に定義できます。

Range<Integer> analogChannelRange = new Range<>(v -> v == 1 || v == 3 || v == 4 || v == 6 || v == 8 || v == 10 || v == 12);

ちょっと不恰好なので

List<Integer> validChannels = Arrays.asList(1, 3, 4, 6, 8, 10, 12);
Range<Integer> analogChannelRange = new Range<>(v -> validChannels.contains(v));

とするのもよいでしょう。この書き換えでは実質的finalを使っています。


4ヶ月前に更新