Skip to content

Instantly share code, notes, and snippets.

@andrebreves
Last active February 14, 2025 02:13
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.concurrent.locks.ReentrantLock;
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 ReentrantLock lock = new ReentrantLock();
private LazyOptional(Supplier<Optional<T>> optionalSupplier, boolean emptyOnException) {
this.optionalSupplier = Objects.requireNonNull(optionalSupplier);
this.suppressExceptions = emptyOnException;
}
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<? extends T> valueSupplier) {
Objects.requireNonNull(valueSupplier);
return new LazyOptional<>(() -> Optional.ofNullable(valueSupplier.get()), false);
}
public static <T> LazyOptional<T> fromOptional(Supplier<Optional<T>> optionalSupplier) {
Objects.requireNonNull(optionalSupplier);
return new LazyOptional<>(optionalSupplier, false);
}
public static <T> LazyOptional<T> fromOptional(Optional<T> optional) {
Objects.requireNonNull(optional);
return optional.isEmpty()
? empty()
: new LazyOptional<>(() -> optional, false);
}
private Optional<T> maybeComputeOptional() {
ReentrantLock _lock = lock;
if (_lock != null) {
_lock.lock();
try {
if (lock != null) {
optional = optionalSupplier.get();
}
} catch (Throwable t) {
optional = Optional.empty();
if (!suppressExceptions) throw t;
} finally {
lock = null;
_lock.unlock();
}
}
return optional;
}
// Terminal Operations
public Optional<T> toOptional() {
if (lock == null) 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 (this == obj) return true;
if (!getClass().isInstance(obj)) return false;
var other = getClass().cast(obj);
return toOptional().equals(other.toOptional());
}
@Override
public int hashCode() {
return toOptional().hashCode();
}
// Non-terminal Operations
public LazyOptional<T> emptyOnException() {
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 Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
return new LazyOptional<>(() -> toOptional().flatMap(mapper), suppressExceptions);
}
public <U> LazyOptional<U> flatMapLazy(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