< prev index next > src/java.base/share/classes/java/util/WeakHashMap.java
Print this page
* questions.
*/
package java.util;
+ import sun.security.action.GetPropertyAction;
+
+ import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
/**
* Hash table based implementation of the {@code Map} interface, with
* <em>weak keys</em>.
* An entry in a {@code WeakHashMap} will automatically be removed when
- * its key is no longer in ordinary use. More precisely, the presence of a
+ * its key is no longer in ordinary use.
+ * More precisely, for keys that are identity objects, the presence of a
* mapping for a given key will not prevent the key from being discarded by the
* garbage collector, that is, made finalizable, finalized, and then reclaimed.
+ * For keys that are {@linkplain Class#isValue() value objects}, the retention of the
+ * key and value depends on the {@link ValuePolicy} for the {@code WeakHashMap} and
+ * the garbage collection handling of objects that are linked by {@link SoftReference}.
* When a key has been discarded its entry is effectively removed from the map,
* so this class behaves somewhat differently from other {@code Map}
* implementations.
*
+ * <p>
+ * Keys that are {@linkplain Class#isValue() value objects} do not have identity and cannot be
+ * the referent in any {@link java.lang.ref.Reference} including {@link WeakReference}.
+ * The retention of entries with keys that are value objects is selected
+ * using {@link ValuePolicy} when the {@code WeakHashMap} is created.
+ * The default {@code ValuePolicy} is {@link ValuePolicy#defaultValuePolicy()}.
+ * The retention modes implemented by {@link #put(Object, Object) WeakHashMap.put(k,v)} are:
+ * <UL>
+ * <LI> {@linkplain ValuePolicy#STRONG SOFT} - entries have a lifetime similar to
+ * referents of {@link SoftReference},
+ * <LI> {@linkplain ValuePolicy#STRONG STRONG} - entries are retained until removed,
+ * <LI> {@linkplain ValuePolicy#STRONG DISCARD} - entries are discarded and not put in the map,
+ * <LI> {@linkplain ValuePolicy#STRONG THROW} - entries are not inserted and
+ * {@link #put(Object, Object) put(k,v)} throws {@link IdentityException}
+ * </UL>
+ *
* <p> Both null values and the null key are supported. This class has
* performance characteristics similar to those of the {@code HashMap}
* class, and has the same efficiency parameters of <em>initial capacity</em>
* and <em>load factor</em>.
*
* <p> Like most collection classes, this class is not synchronized.
* A synchronized {@code WeakHashMap} may be constructed using the
* {@link Collections#synchronizedMap Collections.synchronizedMap}
* method.
*
- * <p> This class is intended primarily for use with key objects whose
+ * <p> <i>Update needed for Value Objects:
+ * <br>This class is intended primarily for use with key objects whose
* {@code equals} methods test for object identity using the
* {@code ==} operator. Once such a key is discarded it can never be
* recreated, so it is impossible to do a lookup of that key in a
* {@code WeakHashMap} at some later time and be surprised that its entry
* has been removed. This class will work perfectly well with key objects
* whose {@code equals} methods are not based upon object identity, such
* as {@code String} instances. With such recreatable key objects,
* however, the automatic removal of {@code WeakHashMap} entries whose
- * keys have been discarded may prove to be confusing.
+ * keys have been discarded may prove to be confusing.</i>
*
* <p> The behavior of the {@code WeakHashMap} class depends in part upon
* the actions of the garbage collector, so several familiar (though not
* required) {@code Map} invariants do not hold for this class. Because
* the garbage collector may discard keys at any time, a
*
* @see ConcurrentModificationException
*/
int modCount;
+ // Current policy with regard to keys that are Value classes.
+ private final ValuePolicy valuePolicy;
+
@SuppressWarnings("unchecked")
private Entry<K,V>[] newTable(int n) {
return (Entry<K,V>[]) new Entry<?,?>[n];
}
/**
* Constructs a new, empty {@code WeakHashMap} with the given initial
* capacity and the given load factor.
+ * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+ * default policy for value objects}.
*
* @apiNote
* To create a {@code WeakHashMap} with an initial capacity that accommodates
* an expected number of mappings, use {@link #newWeakHashMap(int) newWeakHashMap}.
*
* @param loadFactor The load factor of the {@code WeakHashMap}
* @throws IllegalArgumentException if the initial capacity is negative,
* or if the load factor is nonpositive.
*/
public WeakHashMap(int initialCapacity, float loadFactor) {
+ this(initialCapacity, loadFactor, ValuePolicy.DEFAULT_VALUE_POLICY);
+ }
+
+ /**
+ * Constructs a new, empty {@code WeakHashMap} with the given initial
+ * capacity, the given load factor, and value policy.
+ *
+ * @param initialCapacity The initial capacity of the {@code WeakHashMap}
+ * @param loadFactor The load factor of the {@code WeakHashMap}
+ * @param valuePolicy The {@link ValuePolicy} for keys that are value objects
+ * @throws IllegalArgumentException if the initial capacity is negative,
+ * or if the load factor is nonpositive.
+ * @throws NullPointerException if {@code valuePolicy} is null
+ */
+ public WeakHashMap(int initialCapacity, float loadFactor, ValuePolicy valuePolicy) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Initial Capacity: "+
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load factor: "+
loadFactor);
+ this.valuePolicy = Objects.requireNonNull(valuePolicy, "valuePolicy");
int capacity = HashMap.tableSizeFor(initialCapacity);
table = newTable(capacity);
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
}
/**
* Constructs a new, empty {@code WeakHashMap} with the given initial
* capacity and the default load factor (0.75).
+ * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+ * default policy for value objects}.
*
* @apiNote
* To create a {@code WeakHashMap} with an initial capacity that accommodates
* an expected number of mappings, use {@link #newWeakHashMap(int) newWeakHashMap}.
*
}
/**
* Constructs a new, empty {@code WeakHashMap} with the default initial
* capacity (16) and load factor (0.75).
+ * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+ * default policy for value objects}.
*/
public WeakHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
+ /**
+ * Constructs a new, empty {@code WeakHashMap} with the {@link ValuePolicy},
+ * the default initial capacity (16) and load factor (0.75).
+ *
+ * @param valuePolicy The {@link ValuePolicy} for keys that are value objects; non-null
+ * @throws NullPointerException if {@code valuePolicy} is null
+ */
+ public WeakHashMap(ValuePolicy valuePolicy) {
+ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, valuePolicy);
+ }
+
/**
* Constructs a new {@code WeakHashMap} with the same mappings as the
* specified map. The {@code WeakHashMap} is created with the default
* load factor (0.75) and an initial capacity sufficient to hold the
* mappings in the specified map.
+ * The {@code WeakHashMap} is created using the {@linkplain ValuePolicy#defaultValuePolicy()
+ * default policy for value objects}.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
* @since 1.3
*/
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR);
putAll(m);
}
+ /**
+ * {@return the {@link ValuePolicy} for this WeakHashMap.}
+ */
+ public ValuePolicy valuePolicy() {
+ return valuePolicy;
+ }
+
// internal utilities
/**
* Value representing null keys inside tables.
*/
* default uses Object.equals.
*/
private boolean matchesKey(Entry<K,V> e, Object key) {
// check if the given entry refers to the given key without
// keeping a strong reference to the entry's referent
- if (e.refersTo(key)) return true;
+ // only identity objects can be compared to a reference
+ if (Objects.hasIdentity(key) && e.refersTo(key)) return true;
// then check for equality if the referent is not cleared
Object k = e.get();
return k != null && key.equals(k);
}
* @param value value to be associated with the specified key.
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key}.)
+ * @throws IdentityException if {@code key} is a value object
+ * and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
*/
public V put(K key, V value) {
Object k = maskNull(key);
+ final boolean hasIdentity = Objects.hasIdentity(k);
+ if (!hasIdentity && valuePolicy == ValuePolicy.DISCARD) {
+ // put of a value object key with value policy DISCARD is more like remove(key)
+ return remove(key);
+ }
int h = hash(k);
Entry<K,V>[] tab = getTable();
int i = indexFor(h, tab.length);
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
e.value = value;
return oldValue;
}
}
- modCount++;
Entry<K,V> e = tab[i];
- tab[i] = new Entry<>(k, value, queue, h, e);
+ e = hasIdentity ? new Entry<>(k, value, queue, h, e) : newValueEntry(k, value, queue, h, e);
+
+ modCount++;
+ tab[i] = e;
if (++size > threshold)
resize(tab.length * 2);
return null;
}
+ /**
+ * Return a new entry for keys that are value objects.
+ * The {@link ValuePolicy} for this WeakHashMap determines what entry is returned.
+ * <ul>
+ * <li> THROW - Throws an IdentityException</li>
+ * <li> STRONG - a StrongEntry </li>
+ * <li> SOFT - a SoftEntry</li>
+ * <li> DISCARD - null</li>
+ * </ul>
+ *
+ * @param key key with which the specified value is to be associated; non-null
+ * @param value value to be associated with the specified key
+ * @param queue queue
+ * @param hash hash
+ * @param next next
+ * @return a new entry or null to discard
+ * @throws IdentityException if the valuePolicy is {@link ValuePolicy#THROW}
+ */
+ private Entry<K, V> newValueEntry(Object key, V value,
+ ReferenceQueue<Object> queue,
+ int hash, Entry<K,V> next) {
+ return switch (valuePolicy) {
+ case THROW -> throw new IdentityException(key.getClass());
+ case STRONG -> StrongEntry.newStrongEntry(key, value, queue, hash, next);
+ case SOFT -> SoftEntry.newSoftEntry(key, value, queue, hash, next);
+ case DISCARD -> null;
+ };
+ }
+
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* These mappings will replace any mappings that this map had for any
* of the keys currently in the specified map.
*
* @param m mappings to be stored in this map.
* @throws NullPointerException if the specified map is null.
+ * @throws IdentityException if any of the {@code keys} is a value object
+ * and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
*/
public void putAll(Map<? extends K, ? extends V> m) {
int numKeysToBeAdded = m.size();
if (numKeysToBeAdded == 0)
return;
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
+ /**
+ * {@inheritDoc}
+ * @param key {@inheritDoc}
+ * @param value {@inheritDoc}
+ * @return {@inheritDoc}
+ *
+ * @throws IdentityException if {@code key} is a value object
+ * and the {@link #valuePolicy() valuePolicy} is {@link ValuePolicy#THROW}.
+ */
+ public V putIfAbsent(K key, V value) {
+ V v = get(key);
+ if (v == null) {
+ v = put(key, value);
+ }
+
+ return v;
+ }
+
/**
* Removes the mapping for a key from this weak hash map if it is present.
* More formally, if this map contains a mapping from key {@code k} to
* value {@code v} such that <code>(key==null ? k==null :
* key.equals(k))</code>, that mapping is removed. (The map can contain
public String toString() {
return getKey() + "=" + getValue();
}
}
+ /**
+ * A SoftEntry is used for value class keys in which the entries are retained
+ * until there is some memory pressure. An anchor object is used as the referent
+ * of a SoftReference and also as the referent of the WeakReference.
+ * After the SoftReference is cleared, due to GC pressure, the WeakReference is cleared too.
+ *
+ * @param <K> key
+ * @param <V> value
+ */
+ private static class SoftEntry<K, V> extends Entry<K, V> {
+ final Object realKey;
+ // SoftReference to the anchor to keep it alive until GC clears the SoftReference
+ private final SoftReference<Object> softAnchor;
+
+ static <K, V> SoftEntry<K, V> newSoftEntry(Object key, V value,
+ ReferenceQueue<Object> queue,
+ int hash, Entry<K, V> next) {
+ // Select a new anchor object; the entry will be retained until the anchor is collected
+ Object anchor = new Object();
+ return new SoftEntry<>(anchor, key, value, queue, hash, next);
+ }
+
+ private SoftEntry(Object anchor, Object key, V value,
+ ReferenceQueue<Object> queue,
+ int hash, Entry<K,V> next) {
+ super(anchor, value, queue, hash, next);
+ this.realKey = key;
+ this.softAnchor = new SoftReference<>(anchor);
+ }
+
+ /**
+ * The real key is not the referent.
+ * {{@inheritDoc}}
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public K get() {
+ return (K) realKey;
+ }
+
+ @SuppressWarnings("unchecked")
+ public K getKey() {
+ return (K) realKey;
+ }
+ }
+
+ /**
+ * A StrongEntry is used for value class keys in which the entries are retained
+ * until removed. A singleton instance is used as the referent of the WeakReference.
+ * Since the anchor is never reclaimed, the Entry is retained forever.
+ *
+ * @param <K> key
+ * @param <V> value
+ */
+ private static class StrongEntry<K, V> extends Entry<K, V> {
+ final Object realKey;
+
+ // A permanent strong reference to an Object
+ private static final Object STRONG_ANCHOR = new Object();
+
+ static <K, V> StrongEntry<K, V> newStrongEntry(Object key, V value,
+ ReferenceQueue<Object> queue,
+ int hash, Entry<K, V> next) {
+ return new StrongEntry<>(STRONG_ANCHOR, key, value, queue, hash, next);
+ }
+
+ private StrongEntry(Object anchor, Object key, V value,
+ ReferenceQueue<Object> queue,
+ int hash, Entry<K,V> next) {
+ super(anchor, value, queue, hash, next);
+ this.realKey = key;
+ }
+
+ /**
+ * The real key is not the referent.
+ * {{@inheritDoc}}
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public K get() {
+ return (K) realKey;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public K getKey() {
+ return (K) realKey;
+ }
+ }
+
private abstract class HashIterator<T> implements Iterator<T> {
private int index;
private Entry<K,V> entry;
private Entry<K,V> lastReturned;
private int expectedModCount = modCount;
throw new IllegalArgumentException("Negative number of mappings: " + numMappings);
}
return new WeakHashMap<>(HashMap.calculateHashMapCapacity(numMappings));
}
+ /**
+ * Enum for the ValuePolicy; when putting a key and value into a WeakHashMap
+ * determines how keys that are value objects are retained (or not).
+ * The default {@code ValuePolicy} is {@link ValuePolicy#SOFT}.
+ * @since Valhalla
+ */
+ public enum ValuePolicy {
+ /**
+ * If the key is a value object, retain the key and value until removed or
+ * there is memory pressure that causes soft references to be cleared.
+ */
+ SOFT,
+ /**
+ * If the key is a value object, retain the key and value until removed.
+ */
+ STRONG,
+ /**
+ * If the key is a value object, discard the key and value immediately;
+ * such keys and values are not retained.
+ */
+ DISCARD,
+ /**
+ * If the key is a value object, throw {@link IdentityException};
+ * such keys and values are not retained.
+ */
+ THROW;
+
+ /**
+ * {@return the default ValuePolicy}
+ *
+ * The default {@code ValuePolicy} is {@link ValuePolicy#SOFT} unless overridden by
+ * the system property {@systemProperty java.util.WeakHashMap.valueKeyRetention}.
+ * If the property is set to the name of a {@code ValuePolicy} enum,
+ * the default {@code ValuePolicy} is set using {@link ValuePolicy#valueOf(String)}.
+ * If the property value is absent or not valid, the policy is set to {@link ValuePolicy#SOFT}.
+ */
+ public static ValuePolicy defaultValuePolicy() {
+ return DEFAULT_VALUE_POLICY;
+ }
+
+ // System property name for the default ValuePolicy
+ private static final String WEAK_HASH_MAP_VALUE_KEY_RETENTION =
+ "java.util.WeakHashMap.valueKeyRetention";
+
+ // Default WeakHashMap ValuePolicy for keys that are value objects
+ private static final ValuePolicy DEFAULT_VALUE_POLICY = initDefaultValuePolicy();
+
+ /**
+ * {@return the default policy for retention of keys that are value classes}
+ * If the system property "java.util.WeakHashMap.valueKeyRetention"
+ * is the name of a {@link ValuePolicy} enum return it,
+ * otherwise return {@link ValuePolicy#THROW}.
+ */
+ private static ValuePolicy initDefaultValuePolicy() {
+ try {
+ String p = GetPropertyAction
+ .privilegedGetProperty(WEAK_HASH_MAP_VALUE_KEY_RETENTION);
+ if (p != null) {
+ return ValuePolicy.valueOf(p);
+ }
+ } catch (IllegalArgumentException ex) {
+ }
+
+ return THROW; // hardcoded default if property not set
+ }
+ }
+
}
< prev index next >