Skip to content

Instantly share code, notes, and snippets.

@Bill Bill/Memoize.java
Last active May 24, 2019

Embed
What would you like to do?
A couple memoization functions for Java Suppliers.
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
class Memoize {
private Memoize() {}
/*
Props to AOL Cyclops Memoize for this little trick: using the UNSET reference
value as the sentinel for an uninitialized cache value. This approach allows memoization
to support suppliers (original.get()) that return null.
*/
private final static Object UNSET = new Object();
/**
* Memoize a {@link Supplier}. Returns a new supplier based on the one provided. The first time
* {@code get()} is called on the new supplier, it'll delegate to {@code delegate.get()} and
* remember the result, and return it. Subsequent calls will simply return the remembered result.
*
* The returned {@link Supplier} is not safe for use from multiple threads.
*
* @param delegate is the original {@link Supplier}.
* @param <T> the kind of object returned by {@link Supplier#get()}
* @return a new {@link Supplier}. Calling {@code get()} on that supplier will always return the
* result of the first call to {@code delegate.get()}. That returned value may be null.
*/
@SuppressWarnings("unchecked")
static <T> Supplier<T> memoizeNotThreadSafe(final Supplier<T> delegate) {
// I'd like this to be T[] but that would prevent static initialization
final Object[] cache = {UNSET};
return () -> cache[0] == UNSET ? (T)(cache[0] = delegate.get()) : (T)cache[0];
}
/**
* Memoize a {@link Supplier}. Returns a new supplier based on the one provided. The first time
* {@code get()} is called on the new supplier, it'll delegate to {@code delegate.get()} and
* remember the result, and return it. Subsequent calls will simply return the remembered result.
*
* The returned {@link Supplier} is safe for use by multiple threads, provided
* {@code delegate.get()} is side-effect free. Explanation: if multiple threads call
* {@link Supplier#get()} on the returned {@link Supplier} then the
* original {@link Supplier}'s {@link Supplier#get()} may be invoked more than once.
* So make sure{@code delegate.get()} is side-effect free.
*
* @param delegate is the original {@link Supplier}.
* @param <T> the kind of object returned by {@link Supplier#get()}
* @return a new {@link Supplier}. Calling {@code get()} on that supplier will always return the
* result of the first call to {@code delegate.get()}. That returned value may be null.
*/
static <T> Supplier<T> memoize(final Supplier<T> delegate) {
@SuppressWarnings("unchecked") final AtomicReference<T>
cache =
new AtomicReference<>((T) UNSET);
return () -> {
T result = cache.get();
if (result == UNSET) {
result = cache.updateAndGet(oldValue -> oldValue == UNSET ?
delegate.get() : oldValue);
}
return result;
};
}
public static void main(final String[] args) {
final int[] state = new int[]{0};
final Supplier<Integer> integerSupplier = () -> state[0] = state[0] + 1;
testMemoizedSupplier(memoizeNotThreadSafe(integerSupplier), state);
state[0] = 0;
testMemoizedSupplier(memoize(integerSupplier), state);
}
private static void testMemoizedSupplier(final Supplier<Integer> memoized, final int[] state) {
assertThat(state[0], 0, "initial state");
assertThat(memoized.get(), 1, "first get()");
assertThat(memoized.get(), 1, "second get()");
assertThat(state[0], 1, "final state");
}
private static void assertThat(final int val, final int expect, final String message) {
if (val != expect) {
throw new AssertionError(String.format("failed '%s': %s != %s", message, val, expect));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.