独自のOptionalを定義する

Optionalへの不満

最近とみにOptionalの便利さを実感してきているのだが、しかし不満がある。

Serializableではない

不要な人には不要だろうが、必要な者には必要なのである。これだけはどうにもならず、シリアライゼーションするにはOptionalをわざわざ、orElse(null)としなければならないわけだ。

逆にシリアライゼーション復帰の際は、Optional.ofNullable(value)としなければならない。とても面倒だ。

Java9, Java10で機能追加されている

ターゲットはJava8なのだが、特にJava9で追加されたifPresentOrElseは欲しいところ。しかし使えない。その他にも様々なメソッドが追加されている。この位はなから用意できなかったのだろうか?あまりにも想像力がなさすぎると思う。

独自のOptional

そこで独自のOptionalを作ってしまう。とはいっても単にJava10のOptionalのまるコピーだ。

package somePackage;

import java.io.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

public final class Opt<T> implements Serializable {
  private static final long serialVersionUID = 1L;

  private static final Opt<?> EMPTY = new Opt<>();
  private final T value;

  private Opt() {
    this.value = null;
  }

  public static <T> Opt<T>fromOptional(Optional<T>opt) {
    return Opt.ofNullable(opt.orElse(null));
  }

  public Optional<T>toOptional() {
    return Optional.ofNullable(value);
  }

  public static <T> Opt<T> empty() {
    @SuppressWarnings("unchecked")
    Opt<T> t = (Opt<T>) EMPTY;
    return t;
  }

  private Opt(T value) {
    this.value = Objects.requireNonNull(value);
  }

  public static <T> Opt<T> of(T value) {
    return new Opt<>(value);
  }

  public static <T> Opt<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
  }

  public T get() {
    if (value == null) {
      throw new NoSuchElementException("No value present");
    }
    return value;
  }

  public boolean isPresent() {
    return value != null;
  }

  public void ifPresent(Consumer<? super T> action) {
    if (value != null) {
      action.accept(value);
    }
  }

  public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    if (value != null) {
      action.accept(value);
    } else {
      emptyAction.run();
    }
  }

  public Opt<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
      return this;
    } else {
      return predicate.test(value) ? this : empty();
    }
  }

  public <U> Opt<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
      return empty();
    } else {
      return Opt.ofNullable(mapper.apply(value));
    }
  }

  public <U> Opt<U> flatMap(Function<? super T, ? extends Opt<? extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
      return empty();
    } else {
      @SuppressWarnings("unchecked")
      Opt<U> r = (Opt<U>) mapper.apply(value);
      return Objects.requireNonNull(r);
    }
  }

  public Opt<T> or(Supplier<? extends Opt<? extends T>> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
      return this;
    } else {
      @SuppressWarnings("unchecked")
      Opt<T> r = (Opt<T>) supplier.get();
      return Objects.requireNonNull(r);
    }
  }

  public Stream<T> stream() {
    if (!isPresent()) {
      return Stream.empty();
    } else {
      return Stream.of(value);
    }
  }

  public T orElse(T other) {
    return value != null ? value : other;
  }

  public T orElseGet(Supplier<? extends T> supplier) {
    return value != null ? value : supplier.get();
  }

  public T orElseThrow() {
    if (value == null) {
      throw new NoSuchElementException("No value present");
    }
    return value;
  }

  public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
      return value;
    } else {
      throw exceptionSupplier.get();
    }
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }

    if (!(obj instanceof Opt)) {
      return false;
    }

    Opt<?> other = (Opt<?>) obj;
    return Objects.equals(value, other.value);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(value);
  }

  @Override
  public String toString() {
    return value != null ? String.format("Opt[%s]", value) : "Opt.empty";
  }
}

ただし、メソッドを追加してることに注意。Optionalからの変換と逆変換だ。単純に以下のようなものである。

  public static <T> Opt<T>fromOptional(Optional<T>opt) {
    return Opt.ofNullable(opt.orElse(null));
  }

  public Optional<T>toOptional() {
    return Optional.ofNullable(value);
  }