Last active
February 14, 2025 02:13
-
-
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.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