プロジェクト

全般

プロフィール

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

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

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

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

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

まず、以前の日記で書いた、引数に数値の範囲を取り、範囲内外の判定をするクラス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を使っています。

クリップボードから画像を追加 (サイズの上限: 1 GB)