Class ConstantCache<T>

java.lang.Object
eu.bandm.tools.util.uni.ConstantCache<T>
Type Parameters:
T - the type of constant value objects

public class ConstantCache<T> extends Object
Caching for unique constant value objects.

A constant value object is an object that never during its lifetime changes its observable behavior with respect to equals and hashCode. Constant value objects are the only ones safe to use as elements in a Set or keys in a Map data structure, respectively. An object acquires this property either statically by virtue of its class (which enforces immutability), or dynamically by being left de-facto unchanged in a running application.

(De)Duplication

A pair of constant value object references x and y such that x.equals(y) is true, but x == y is false, is called a duplicate. In many situations, duplicates are nothing but redundant, and a waste of space (for storage) and time (for deep comparison). Various mechanisms, such as String.intern(), Integer.valueOf(int) and Enum, address the need for avoiding duplicates of certain specific types.

ConstantCache objects provide a generic and local service to avoid duplicates: They can be used for any value type, and they manage duplicates selectively, only during their lifetime and among their respective clients.

The provided mechanism is analogous to String.intern(): a prototype object is deduplicated, i.e., replaced with a unique representative of its equivalence class. The creation of duplicates is not avoided in the first place, but precedence is given to the longer-lived representative, allowing for timely garbage collection of the prototype.

Memory Manangement

The space leak associated with the backing map of representatives is controlled by the reachability of the cache; if there are no more clients, the map (and possibly the stored representatives) can be garbage-collected.

It is recommended not to store references to caches in static variables.

Type Precision

Deduplication interacts nicely with the type system only if the type hierarchy of constant value objects is trivial (as in the case of String or Integer), or heavily restricted (as in the case of Enum). In general, the contract of equals does not guarantee that x.equals(y) entails any relationship between the classes of x and y. The ConstantCache API solves the problem with a two-level approach:

  1. A base type for constant value objects in a particular cache is specified by the type parameter T. The weakly typed access operation deduplicate(T) preserves only this type. For flat hierarchies (such as String), this gives already sufficient type precision.
  2. The strongly typed access operation deduplicateExact(U) safely preserves the type of the given prototype by casting the representative. A ClassCastException ensues if the representative is not of the expected type.

To avoid unexpected exceptions from deduplicateExact(U), it is recommended that the latter mode of access be used only with constant value types where x.equals(y) entails x.getClass() == y.getClass(). This is implied if every class in the type hierarchy is either abstract or (effectively) final, which is common for data models. Note that the anonymous initializer idiom new A(){{...}} may violate such a requirement.

Concurrency

Instances of this class are not thread-safe by default. Use the factory method synchronizedConstantCache(ConstantCache<T>) to create a thread-safe proxy.

Usage Example

Create an instance of ConstantCache to establish a scope for deduplication. Share references to it among all parts of the running application that shall be included in the scope:


   final ConstantCache<MyData> cc = new ConstantCache<>();
   myContext.setConstantCache(cc);
 

The actual deduplication protocol is cooperative: The recommended idiom is to literally wrap every expression that creates a constant value prototype in deduplicate (or deduplicateExact), such that no reference to the prototype can escape:


   MyData d1 = cc.deduplicate(new MyData(...));
   MyData d2 = cc.deduplicate(MyData.of(...));
 

A class of constant value objects can enforce preemptive deduplication by hiding all constructors, and exposing only factory methods that deduplicate new instances (in some appropriate scope) before returning them to the caller.

Equality and Identity

If two references x and y can be assumed to have undergone deduplication (in the same scope), then x.equals(y) can be replaced by x == y. However:

  1. This may lead to subtle errors if the assumption is wrong.
  2. The special case of identity is handled as a quick heuristic in many implementations of equals anyway, as a literal implementation of the reflexivity clause in the contract specified by Object.equals.

Therefore it is recommended to retain the use of equals for all comparisons of deduplicated constant value objects. The performance overhead is likely negligible in practice.

  • Constructor Details

    • ConstantCache

      ConstantCache(Map<T,T> cache)
      Creates a new instance. Do not call this constructor directly!
      Parameters:
      cache - the map to store representatives in
    • ConstantCache

      public ConstantCache()
      Creates a new, initially empty cache.
  • Method Details

    • synchronizedConstantCache

      public static <T> ConstantCache<T> synchronizedConstantCache(ConstantCache<T> back)
      Creates a thread-safe proxy cache backed by the given cache.

      Note that accessing the underlying cache directly circumvents thread safety. It is recommended not to retain any extra references.

      Type Parameters:
      T - the type of constant value objects
      Parameters:
      back - the backing cache
      Returns:
      a cache that forwards requests to back, but protects against concurrent access conflicts
      See Also:
    • deduplicate

      public T deduplicate(T prototype)
      Returns a unique constant value object that is equal to the given one.

      This method tolerates null references; the value null has no duplicates by definition.

      If Objects.equals(x, y) is true, then both Objects.equals(x, deduplicate(x)) and deduplicate(x) == deduplicate(y) are true also.

      It follows that deduplication is idempotent: deduplicate(deduplicate(x)) == deduplicate(x) is true also.

      Parameters:
      prototype - a constant value object, or null
      Returns:
      a unique representative constant value object equal to prototype, or null, respectively
    • deduplicateExact

      public <U extends T> U deduplicateExact(U prototype)
      Returns a unique constant value object that is equal to the given one.

      This method tolerates null references; the value null has no duplicates by definition.

      If Objects.equals(x, y) is true, then both Objects.equals(x, deduplicate(x)) and deduplicateExact(x) == deduplicateExact(y) are true also.

      It follows that deduplication is idempotent: deduplicateExact(deduplicateExact(x)) == deduplicateExact(x) is true also.

      Type Parameters:
      U - the particular type of the given constant value object
      Parameters:
      prototype - a constant value object, or null
      Returns:
      a unique representative constant value object equal to prototype, or null, respectively
      Throws:
      ClassCastException - if the result is not type-compatible with the prototype