1
0

More than 3 years have passed since last update.

二番煎じ:JavaでIf式をしてみる

Last updated at Posted at 2020-04-26

JavaでIf式をしてみる」の二番煎じ。

実現できること

例えば FizzBuzz を次のように書ける。

    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = 
                    If.<String>test(() -> count % 15 == 0).then(() -> "FizzBuzz")
                    .elif(() -> count % 3 == 0).then(() -> "Fizz")
                    .elif(() -> count % 5 == 0).then(() -> "Buzz")
                    .el(() -> Integer.toString(count));
            System.out.println(fizzBuzzed);
        }
    }

準備

if 式の値として nullable な値を扱うため、
次の汎用クラスを作成する。

import java.util.function.Supplier;

/**
 * null も値として取れる {@link java.util.Optional}。
 * 
 * 今回使わないメソッドについては省略。
 */
public class MayBe<T> {

    /**
     * 値が存在するインスタンスを生成する。
     */
    public static <R> MayBe<R> of(R result) {
        return new MayBe<>(true, result);
    }

    /**
     * 値が存在しないインスタンスを生成する。
     */
    public static <R> MayBe<R> empty() {
        return new MayBe<>(false, null);
    }

    private final boolean isPresent;
    private final T value;

    private MayBe(boolean isPresent, T value) {
        this.isPresent = isPresent;
        this.value = value;
    }

    /**
     * 値が存在するかどうかを返す。
     */
    public boolean isPresent() {
        return this.isPresent;
    }

    /**
     * 値が存在すればそれを返し、存在しなければ引数 other から得た値を返す。
     */
    public T orElseGet(Supplier<T> other) {
        return isPresent() ? this.value : other.get();
    }
}

本番

import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

public class If<R> {

    public static <R> If<R> test(BooleanSupplier predicate) {
        return new If<>(predicate, null);
    }

    private final If<R> prev;
    private final BooleanSupplier predicate;

    private Then<R> then = null;

    /**
     * @param predicate この if の述語。
     * @param prev      この if が else if である場合、1つ前の if。
     *                  そうでない場合、null。
     */
    private If(BooleanSupplier predicate, If<R> prev) {
        this.prev = prev;
        this.predicate = predicate;
    }

    public Then<R> then(Supplier<R> valueSupplier) {
        if (this.then != null) {
            throw new IllegalStateException("`then` が既に呼び出されています。");
        }

        return this.then = new Then<>(this, valueSupplier);
    }

    /**
     * この if までを評価する。
     * 
     * @return 評価した結果の値。
     */
    private MayBe<R> eval() {
        if (this.then == null) {
            throw new IllegalStateException("`then` が未だ呼び出されていません。");
        }

        if (this.prev != null) {
            MayBe<R> prevValue = this.prev.eval();
            if (prevValue.isPresent()) {
                return prevValue;
            }
        }

        return this.predicate.getAsBoolean()
                ? MayBe.of(this.then.getThenValue())
                : MayBe.empty();
    }

    /**
     * {@link If#then} が返すオブジェクトのクラス。
     */
    public static class Then<R> {
        private final If<R> relatedIf;
        private final Supplier<R> thenValueSupplier;

        /**
         * @param relatedIf     この then の if。
         * @param valueSupplier この then の if が true である場合に返す値。
         */
        Then(If<R> relatedIf, Supplier<R> valueSupplier) {
            this.relatedIf = relatedIf;
            this.thenValueSupplier = valueSupplier;
        }

        public If<R> elif(BooleanSupplier predicate) {
            return new If<>(predicate, this.relatedIf);
        }

        public R el(Supplier<R> valueSupplier) {
            return this.relatedIf.eval().orElseGet(valueSupplier);
        }

        /**
         * この then の if が true の場合の値を返す。
         */
        R getThenValue() {
            return this.thenValueSupplier.get();
        }
    }
}

特長

  • if の述語は必要なものだけが評価される。
  • 値の取得も必要なものだけが評価される。
  • else if (elif)が使えるため、入れ子が深くならずに済む。

所感

JavaScript でいう即時関数のようにした方が読みやすいな。

    public static void main(String... args) {
        for (int i = 1; i <= 100; ++i) {
            int count = i;
            String fizzBuzzed = ((Supplier<String>) () -> {
                if (count % 15 == 0) return "FizzBuzz";
                else if (count % 3 == 0) return "Fizz";
                else if (count % 5 == 0) return "Buzz";
                else return Integer.toString(count);
            }).get();
            System.out.println(fizzBuzzed);
        }
    }

/以上

1
0
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
1
0