「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);
}
}
/以上