Last active
January 11, 2022 01:43
-
-
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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