Class ConstantCache<T>
- Type Parameters:
T- the type of 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:
- A base type for constant value objects in a particular cache is
specified by the type parameter
T. The weakly typed access operationdeduplicate(T)preserves only this type. For flat hierarchies (such asString), this gives already sufficient type precision. - The strongly typed access operation
deduplicateExact(U)safely preserves the type of the given prototype by casting the representative. AClassCastExceptionensues 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:
- This may lead to subtle errors if the assumption is wrong.
- The special case of identity is handled as a
quick heuristic in many implementations of
equalsanyway, as a literal implementation of the reflexivity clause in the contract specified byObject.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 Summary
ConstructorsConstructorDescriptionCreates a new, initially empty cache.ConstantCache(Map<T, T> cache) Creates a new instance. -
Method Summary
Modifier and TypeMethodDescriptiondeduplicate(T prototype) Returns a unique constant value object that is equal to the given one.<U extends T>
UdeduplicateExact(U prototype) Returns a unique constant value object that is equal to the given one.static <T> ConstantCache<T> synchronizedConstantCache(ConstantCache<T> back) Creates a thread-safe proxy cache backed by the given cache.
-
Constructor Details
-
ConstantCache
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
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
Returns a unique constant value object that is equal to the given one.This method tolerates null references; the value
nullhas no duplicates by definition.If
Objects.equals(x, y)is true, then bothObjects.equals(x, deduplicate(x))anddeduplicate(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, ornull- Returns:
- a unique representative constant value object equal to
prototype, ornull, respectively
-
deduplicateExact
Returns a unique constant value object that is equal to the given one.This method tolerates null references; the value
nullhas no duplicates by definition.If
Objects.equals(x, y)is true, then bothObjects.equals(x, deduplicate(x))anddeduplicateExact(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, ornull- Returns:
- a unique representative constant value object equal to
prototype, ornull, respectively - Throws:
ClassCastException- if the result is not type-compatible with the prototype
-