Skip to content

Instantly share code, notes, and snippets.

@andrebreves
Last active January 11, 2022 01:43
Show Gist options
  • Save andrebreves/8dabe0579b925921f421b36b8526f790 to your computer and use it in GitHub Desktop.
Save andrebreves/8dabe0579b925921f421b36b8526f790 to your computer and use it in GitHub Desktop.
A type following the Optional API that computes its value once and only after a "terminal operation" (like Stream).
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
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;
import java.util.stream.StreamSupport;
public final class LazyOptional<T> {
private final Supplier<Optional<T>> optionalSupplier;
private final boolean suppressExceptions;
private volatile Optional<T> optional;
private volatile boolean optionalComputed = false;
private LazyOptional(Supplier<Optional<T>> optionalSupplier, boolean suppressExceptions) {
this.optionalSupplier = Objects.requireNonNull(optionalSupplier);
this.suppressExceptions = suppressExceptions;
}
private static final LazyOptional<?> EMPTY = new LazyOptional<>(Optional::empty, false);
@SuppressWarnings("unchecked")
public static <T> LazyOptional<T> empty() {
return (LazyOptional<T>) EMPTY;
}
public static <T> LazyOptional<T> ofNullable(Supplier<T> valueSupplier) {
Objects.requireNonNull(valueSupplier);
return new LazyOptional<>(() -> Optional.ofNullable(valueSupplier.get()), false);
}
public static <T> LazyOptional<T> fromOptional(Optional<T> optional) {
Objects.requireNonNull(optional);
return optional.isEmpty()
? empty()
: new LazyOptional<>(() -> optional, false);
}
private synchronized Optional<T> maybeComputeOptional() {
if (!optionalComputed) {
try {
optional = optionalSupplier.get();
} catch (Throwable t) {
optional = Optional.empty();
if (!suppressExceptions) throw t;
} finally {
optionalComputed = true;
}
}
return optional;
}
// Terminal Operations
public Optional<T> toOptional() {
if (optionalComputed) return optional;
else return maybeComputeOptional();
}
public T get() {
return toOptional().get();
}
public boolean isPresent() {
return toOptional().isPresent();
}
public boolean isEmpty() {
return toOptional().isEmpty();
}
public void ifPresent(Consumer<? super T> action) {
toOptional().ifPresent(action);
}
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
toOptional().ifPresentOrElse(action, emptyAction);
}
public T orElse(T other) {
return toOptional().orElse(other);
}
public T orElseGet(Supplier<? extends T> supplier) {
return toOptional().orElseGet(supplier);
}
public T orElseThrow() {
return toOptional().orElseThrow();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
return toOptional().orElseThrow(exceptionSupplier);
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (obj.getClass() != getClass()) return false;
final LazyOptional<?> other = (LazyOptional<?>) obj;
return toOptional().equals(other.toOptional());
}
@Override
public int hashCode() {
return toOptional().hashCode();
}
// Non-terminal Operations
public LazyOptional<T> suppressExceptions() {
return new LazyOptional<>(() -> toOptional(), true);
}
public LazyOptional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
return new LazyOptional<>(() -> toOptional().filter(predicate), suppressExceptions);
}
public <U> LazyOptional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
return new LazyOptional<>(() -> toOptional().map(mapper), suppressExceptions);
}
public <U> LazyOptional<U> flatMap(Function<? super T, ? extends LazyOptional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
return new LazyOptional<>(() -> toOptional().flatMap(mapper.andThen(LazyOptional::toOptional)), suppressExceptions);
}
public LazyOptional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
Objects.requireNonNull(supplier);
return new LazyOptional<>(() -> toOptional().or(supplier), suppressExceptions);
}
public Stream<T> stream() {
return StreamSupport.stream(new LazyOptionalSpliterator(), false);
}
private final class LazyOptionalSpliterator implements Spliterator<T> {
private static final int CHARACTERISTICS = IMMUTABLE | ORDERED | SIZED | SUBSIZED;
@Override
public int characteristics() {
return CHARACTERISTICS;
}
@Override
public long estimateSize() {
return 1;
}
@Override
public Spliterator<T> trySplit() {
return null;
}
private boolean done = false;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if (!done) {
done = true;
if (isPresent()) {
action.accept(get());
return true;
}
}
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment