Last active
May 24, 2019 21:17
-
-
Save Bill/de30b9f8c10aac2767d84bd00e4b4871 to your computer and use it in GitHub Desktop.
A couple memoization functions for Java Suppliers.
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.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