Class Cache2<K1,K2,V>

java.lang.Object
org.apache.juneau.commons.collections.Cache2<K1,K2,V>
Type Parameters:
K1 - The first key type. Can be an array type for content-based key matching.
K2 - The second key type. Can be an array type for content-based key matching.
V - The value type.

public class Cache2<K1,K2,V> extends Object
Simple in-memory cache for storing and retrieving objects by two-part keys.
Overview:

This class uses ConcurrentHashMap internally to provide a thread-safe caching layer with automatic value computation, cache eviction, and statistics tracking for two-part composite keys. It's designed for caching expensive-to-compute or frequently-accessed objects indexed by two keys.

Features:
  • Thread-safe concurrent access without external synchronization
  • Two-part composite key support
  • Automatic cache eviction when maximum size is reached
  • Lazy computation via Function2 supplier pattern
  • Default supplier support for simplified access
  • Built-in hit/miss statistics tracking
  • Optional logging of cache statistics on JVM shutdown
  • Can be disabled entirely via builder or system property
Usage:

// Create a cache with default supplier Cache2<ClassLoader,Class<?>,ClassMeta> metaCache = Cache2 .of(ClassLoader.class, Class.class, ClassMeta.class) .maxSize(500) .supplier((cl, c) -> createClassMeta(cl, c)) .build(); // Retrieve using default supplier ClassMeta meta1 = metaCache.get(classLoader, String.class); // Or override the supplier ClassMeta meta2 = metaCache.get(classLoader, Integer.class, () -> customMeta);

Cache Behavior:
  • When a key pair is requested:
    • If the key exists in the cache, the cached value is returned (cache hit)
    • If the key doesn't exist, the supplier is invoked to compute the value
    • The computed value is stored in the cache and returned (cache miss)
  • When the cache exceeds Cache2.Builder.maxSize(int), the entire cache is cleared
  • If the cache is disabled, the supplier is always invoked without caching
  • Null keys always bypass the cache and invoke the supplier
Environment Variables:

The following system properties can be used to configure default cache behavior:

  • juneau.cache.mode - Cache mode: NONE/WEAK/FULL (default: FULL, case-insensitive)
  • juneau.cache.maxSize - Maximum cache size before eviction (default: 1000)
  • juneau.cache.logOnExit - Log cache statistics on shutdown (default: false)
Thread Safety:

This class is thread-safe and can be safely used from multiple threads without external synchronization. However, note that when the cache is cleared due to exceeding max size, there's a small window where multiple threads might compute the same value. This is acceptable for most use cases as it only affects performance, not correctness.

Performance Considerations:
Examples:

// Simple cache with defaults Cache2<String,String,Config> cache = Cache2.of(String.class, String.class, Config.class).build(); // Cache with custom configuration Cache2<String,Integer,User> userCache = Cache2 .of(String.class, Integer.class, User.class) .maxSize(500) .logOnExit() .supplier((tenant, id) -> userService.findUser(tenant, id)) .build(); // Disabled cache for testing Cache2<String,String,Object> disabledCache = Cache2 .of(String.class, String.class, Object.class) .disableCaching() .build();

See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    Builder for creating configured Cache2 instances.
  • Constructor Summary

    Constructors
    Modifier
    Constructor
    Description
    protected
    Constructor.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Removes all entries from the cache.
    boolean
    containsKey(K1 key1, K2 key2)
    Returns true if the cache contains a mapping for the specified key pair.
    boolean
    Returns true if the cache contains one or more entries with the specified value.
    static <K1, K2, V> Cache2.Builder<K1,K2,V>
    Creates a new Cache2.Builder for constructing a cache with explicit type parameters.
    get(K1 key1, K2 key2)
    Retrieves a cached value by key pair using the default supplier.
    get(K1 key1, K2 key2, Supplier<V> supplier)
    Retrieves a cached value by key pair, computing it if necessary using the provided supplier.
    int
    Returns the total number of cache hits since this cache was created.
    boolean
    Returns true if the cache contains no entries.
    static <K1, K2, V> Cache2.Builder<K1,K2,V>
    of(Class<K1> key1, Class<K2> key2, Class<V> type)
    Creates a new Cache2.Builder for constructing a cache.
    put(K1 key1, K2 key2, V value)
    Associates the specified value with the specified key pair.
    remove(K1 key1, K2 key2)
    Removes the entry for the specified key pair from the cache.
    int
    Returns the number of entries in the cache.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • Cache2

      protected Cache2(Cache2.Builder<K1,K2,V> builder)
      Constructor.
      Parameters:
      builder - The builder containing configuration settings.
  • Method Details

    • create

      public static <K1, K2, V> Cache2.Builder<K1,K2,V> create()
      Creates a new Cache2.Builder for constructing a cache with explicit type parameters.

      This variant allows you to specify the cache's generic types explicitly without passing the class objects, which is useful when working with complex parameterized types.

      Example:

      // Working with complex generic types Cache2<Class<?>,Class<? extends Annotation>,List<Annotation>> cache = Cache2.<Class<?>,Class<? extends Annotation>,List<Annotation>>create() .supplier((k1, k2) -> findAnnotations(k1, k2)) .build();

      Type Parameters:
      K1 - The first key type.
      K2 - The second key type.
      V - The value type.
      Returns:
      A new builder for configuring the cache.
    • of

      public static <K1, K2, V> Cache2.Builder<K1,K2,V> of(Class<K1> key1, Class<K2> key2, Class<V> type)
      Creates a new Cache2.Builder for constructing a cache.
      Example:

      Cache2<String,Integer,User> cache = Cache2 .of(String.class, Integer.class, User.class) .maxSize(100) .build();

      Type Parameters:
      K1 - The first key type.
      K2 - The second key type.
      V - The value type.
      Parameters:
      key1 - The first key type class (used for type safety).
      key2 - The second key type class (used for type safety).
      type - The value type class.
      Returns:
      A new builder for configuring the cache.
    • clear

      public void clear()
      Removes all entries from the cache.
    • containsKey

      public boolean containsKey(K1 key1, K2 key2)
      Returns true if the cache contains a mapping for the specified key pair.
      Parameters:
      key1 - The first key. Can be null.
      key2 - The second key. Can be null.
      Returns:
      true if the cache contains the key pair.
    • containsValue

      public boolean containsValue(V value)
      Returns true if the cache contains one or more entries with the specified value.
      Parameters:
      value - The value to check.
      Returns:
      true if the cache contains the value.
    • get

      public V get(K1 key1, K2 key2)
      Retrieves a cached value by key pair using the default supplier.

      This method uses the default supplier configured via Cache2.Builder.supplier(Function2). If no default supplier was configured, this method will throw a NullPointerException.

      Example:

      Cache2<String,Integer,User> cache = Cache2 .of(String.class, Integer.class, User.class) .supplier((tenant, id) -> userService.findUser(tenant, id)) .build(); // Uses default supplier User u = cache.get("tenant1", 123);

      Parameters:
      key1 - First key component. Can be null.
      key2 - Second key component. Can be null.
      Returns:
      The cached or computed value. May be null if the supplier returns null.
      Throws:
      NullPointerException - if no default supplier was configured.
    • get

      public V get(K1 key1, K2 key2, Supplier<V> supplier)
      Retrieves a cached value by key pair, computing it if necessary using the provided supplier.

      This method implements the cache-aside pattern:

      1. If the key pair exists in the cache, return the cached value (cache hit)
      2. If the key pair doesn't exist, invoke the supplier to compute the value
      3. Store the computed value in the cache using ConcurrentHashMap.putIfAbsent(Object, Object)
      4. Return the value
      Behavior:
      • If the cache is disabled, always invokes the supplier without caching
      • If the cache exceeds Cache2.Builder.maxSize(int), clears all entries before storing the new value
      • Thread-safe: Multiple threads can safely call this method concurrently
      • The supplier may be called multiple times for the same key pair in concurrent scenarios (due to ConcurrentHashMap.putIfAbsent(Object, Object) semantics)
      Example:

      Cache2<String,Integer,User> cache = Cache2.of(String.class, Integer.class, User.class).build(); // First call: fetches user and caches it User u1 = cache.get("tenant1", 123, () -> userService.findUser("tenant1", 123)); // Second call: returns cached user instantly User u2 = cache.get("tenant1", 123, () -> userService.findUser("tenant1", 123)); assert u1 == u2; // Same instance

      Parameters:
      key1 - First key component. Can be null.
      key2 - Second key component. Can be null.
      supplier - The supplier to compute the value if it's not in the cache. Must not be null.
      Returns:
      The cached or computed value. May be null if the supplier returns null.
    • getCacheHits

      public int getCacheHits()
      Returns the total number of cache hits since this cache was created.

      A cache hit occurs when get(Object, Object) or get(Object, Object, java.util.function.Supplier) finds an existing cached value for the requested key pair, avoiding the need to invoke the supplier.

      Cache Effectiveness:

      You can calculate the cache hit ratio using:

      int hits = cache.getCacheHits(); int misses = cache.size(); int total = hits + misses; double hitRatio = (double) hits / total; // 0.0 to 1.0

      Notes:
      • This counter is never reset, even when clear() is called
      • Thread-safe using AtomicInteger
      • Returns 0 if the cache is disabled
      Returns:
      The total number of cache hits since creation.
    • isEmpty

      public boolean isEmpty()
      Returns true if the cache contains no entries.
      Returns:
      true if the cache is empty.
    • put

      public V put(K1 key1, K2 key2, V value)
      Associates the specified value with the specified key pair.
      Parameters:
      key1 - The first key. Can be null.
      key2 - The second key. Can be null.
      value - The value to associate with the key pair.
      Returns:
      The previous value associated with the key pair, or null if there was no mapping.
    • remove

      public V remove(K1 key1, K2 key2)
      Removes the entry for the specified key pair from the cache.
      Parameters:
      key1 - The first key. Can be null.
      key2 - The second key. Can be null.
      Returns:
      The previous value associated with the key pair, or null if there was no mapping.
    • size

      public int size()
      Returns the number of entries in the cache.
      Returns:
      The number of cached entries.