Caffeine: Use java.util.Optional

Created on 13 Dec 2016  ·  1Comment  ·  Source: ben-manes/caffeine

Despite being a Java 8 only project, Caffeine returns null from a number of methods in its public API. For example, cache.get returns null to represent that no element exists in the cache.

It would be nice if instead an Optional<T> was returned where Optional.empty() represents that no element is in the cache, so that type safety is preserved. null can still be used internally, Optional only needs to be exposed to users of the library.

Most helpful comment

Here are some of the reasons we didn't go down that route.

  1. Incompatibility with reference caching: If stored with the wrapper, then weak and soft values won't behave as desired.
  2. Increased GC pressure: If allocated on a hit this creates a lot of garbage, which isn't often escape analyzed away. For sensitive code that does matter. If stored directly to avoid this despite (1), then it adds memory overhead which is undesirable for large caches.
  3. Negative caching: Sometimes users want to retain a sentinel value to cache a miss. Often an Optional is the most convenient version of this. Unfortunately Java doesn't provide a flatten method for double optionals.
  4. Happy paths: In many cases users know a value will be returned, e.g. directly computable. The check for presence doesn't provide value, which leads to more usage of Optional.get. We tend to recommend users compute through the cache rather than check for presence, where a null indicates non-computable (e.g. record not found in the database).
  5. Non-idiomatic: Both Guava and the Java language team argue for judicious usage, primarily in application than library code, and neither are as full fledged as in other languages. Caffeine (and Guava's cache) were designed to feel like natural extensions to the Collections Framework. In Guava, Josh Bloch argued for disallowing null via checked exceptions so that null hostile API was chosen. But Map.computeIfAbsent didn't follow that idiom, lambdas are not exception friendly, and his advice was more applicable for the Java 4 style code of the day.
  6. Language adapters: Caffeine tries to feel intuitive for a Java developer, but Java styles feel out of place in other JVM languages. A thin adapter, e.g. Scaffeine for Scala, helps to bridge that gap.
  7. Encapsulated usage: Most usages of a cache are internal to a class and not exposed to consumers (an implementation detail). Just like it is common to see immutability to clients with internal mutability for performance, a similar convention makes sense for a cache. As you said, null can still be used internally, Optional only needs to be exposed to users.

I think Optional in Java doesn't fit as cleanly as any of us would like, and many developers would feel it forced upon them undesirably. If you prefer an Optional-based API then I think writing an adapter API is a better route for the time being.

>All comments

Here are some of the reasons we didn't go down that route.

  1. Incompatibility with reference caching: If stored with the wrapper, then weak and soft values won't behave as desired.
  2. Increased GC pressure: If allocated on a hit this creates a lot of garbage, which isn't often escape analyzed away. For sensitive code that does matter. If stored directly to avoid this despite (1), then it adds memory overhead which is undesirable for large caches.
  3. Negative caching: Sometimes users want to retain a sentinel value to cache a miss. Often an Optional is the most convenient version of this. Unfortunately Java doesn't provide a flatten method for double optionals.
  4. Happy paths: In many cases users know a value will be returned, e.g. directly computable. The check for presence doesn't provide value, which leads to more usage of Optional.get. We tend to recommend users compute through the cache rather than check for presence, where a null indicates non-computable (e.g. record not found in the database).
  5. Non-idiomatic: Both Guava and the Java language team argue for judicious usage, primarily in application than library code, and neither are as full fledged as in other languages. Caffeine (and Guava's cache) were designed to feel like natural extensions to the Collections Framework. In Guava, Josh Bloch argued for disallowing null via checked exceptions so that null hostile API was chosen. But Map.computeIfAbsent didn't follow that idiom, lambdas are not exception friendly, and his advice was more applicable for the Java 4 style code of the day.
  6. Language adapters: Caffeine tries to feel intuitive for a Java developer, but Java styles feel out of place in other JVM languages. A thin adapter, e.g. Scaffeine for Scala, helps to bridge that gap.
  7. Encapsulated usage: Most usages of a cache are internal to a class and not exposed to consumers (an implementation detail). Just like it is common to see immutability to clients with internal mutability for performance, a similar convention makes sense for a cache. As you said, null can still be used internally, Optional only needs to be exposed to users.

I think Optional in Java doesn't fit as cleanly as any of us would like, and many developers would feel it forced upon them undesirably. If you prefer an Optional-based API then I think writing an adapter API is a better route for the time being.

Was this page helpful?
0 / 5 - 0 ratings