# HG changeset patch # User mduigou # Date 1365709903 25200 # Node ID f42144dc96e535d1061c8871b5d34cabc532b2eb # Parent 863da64214e826577e48d3aed0b5cfcd858e5595 8004518: Add in-place operations to Map 8010122: Add defaults for ConcurrentMap operations to Map Reviewed-by: darcy, briangoetz, mduigou, dholmes, ulfzibis Contributed-by: Doug Lea
The returned collection does not pass the hashCode - * and equals operations through to the backing collection, but - * relies on Object's equals and hashCode methods. This is + *
The returned collection does not pass the {@code hashCode} + * and {@code equals} operations through to the backing collection, but + * relies on {@code Object}'s equals and hashCode methods. This is * necessary to preserve the contracts of these operations in the case * that the backing collection is a set or a list.
*
@@ -2107,6 +2171,53 @@
public String toString() {
synchronized (mutex) {return m.toString();}
}
+
+ // Override default methods in Map
+ @Override
+ public V getOrDefault(Object k, V defaultValue) {
+ synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
+ }
+ @Override
+ public void forEach(BiConsumer super K, ? super V> action) {
+ synchronized (mutex) {m.forEach(action);}
+ }
+ @Override
+ public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ synchronized (mutex) {m.replaceAll(function);}
+ }
+ @Override
+ public V putIfAbsent(K key, V value) {
+ synchronized (mutex) {return m.putIfAbsent(key, value);}
+ }
+ @Override
+ public boolean remove(Object key, Object value) {
+ synchronized (mutex) {return m.remove(key, value);}
+ }
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ synchronized (mutex) {return m.replace(key, oldValue, newValue);}
+ }
+ @Override
+ public V replace(K key, V value) {
+ synchronized (mutex) {return m.replace(key, value);}
+ }
+ @Override
+ public V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
+ }
+ @Override
+ public V computeIfPresent(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
+ }
+ @Override
+ public V compute(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.compute(key, remappingFunction);}
+ }
+ @Override
+ public V merge(K key, V value, BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ synchronized (mutex) {return m.merge(key, value, remappingFunction);}
+ }
+
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
@@ -2782,6 +2893,70 @@
return entrySet;
}
+ // Override default methods in Map
+ @Override
+ public void forEach(BiConsumer super K, ? super V> action) {
+ m.forEach(action);
+ }
+
+ @Override
+ public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ m.replaceAll(typeCheck(function));
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ typeCheck(key, value);
+ return m.putIfAbsent(key, value);
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ return m.remove(key, value);
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ typeCheck(key, newValue);
+ return m.replace(key, oldValue, newValue);
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ typeCheck(key, value);
+ return m.replace(key, value);
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ Objects.requireNonNull(mappingFunction);
+ return m.computeIfAbsent(key, k -> {
+ V value = mappingFunction.apply(k);
+ typeCheck(k, value);
+ return value;
+ });
+ }
+
+ @Override
+ public V computeIfPresent(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ return m.computeIfPresent(key, typeCheck(remappingFunction));
+ }
+
+ @Override
+ public V compute(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ return m.compute(key, typeCheck(remappingFunction));
+ }
+
+ @Override
+ public V merge(K key, V value, BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+ return m.merge(key, value, (v1, v2) -> {
+ V newValue = remappingFunction.apply(v1, v2);
+ typeCheck(null, newValue);
+ return newValue;
+ });
+ }
+
/**
* We need this class in addition to CheckedSet as Map.Entry permits
* modification of the backing Map via the setValue operation. This
@@ -3456,6 +3631,66 @@
public int hashCode() {return 0;}
+ // Override default methods in Map
+ @Override
+ @SuppressWarnings("unchecked")
+ public V getOrDefault(Object k, V defaultValue) {
+ return defaultValue;
+ }
+
+ @Override
+ public void forEach(BiConsumer super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ }
+
+ @Override
+ public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
// Preserves singleton property
private Object readResolve() {
return EMPTY_MAP;
@@ -3619,6 +3854,64 @@
return values;
}
+ // Override default methods in Map
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ return eq(key, k) ? v : defaultValue;
+ }
+
+ @Override
+ public void forEach(BiConsumer super K, ? super V> action) {
+ action.accept(k, v);
+ }
+
+ @Override
+ public void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V putIfAbsent(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean remove(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean replace(K key, V oldValue, V newValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V replace(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V computeIfPresent(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V compute(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public V merge(K key, V value,
+ BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ throw new UnsupportedOperationException();
+ }
}
// Miscellaneous
diff --git a/src/share/classes/java/util/HashMap.java b/src/share/classes/java/util/HashMap.java
--- a/src/share/classes/java/util/HashMap.java
+++ b/src/share/classes/java/util/HashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,11 @@
*/
package java.util;
+
import java.io.*;
+import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* Hash table based implementation of the Map interface. This
@@ -350,6 +354,13 @@
return null == entry ? null : entry.getValue();
}
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ Entry The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or
+ * {@code defaultValue} if this map contains no mapping for the key
+ * @throws ClassCastException if the key is of an inappropriate type for
+ * this map
+ * (optional)
+ * @throws NullPointerException if the specified key is null and this map
+ * does not permit null keys
+ * (optional)
+ */
+ default V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return (((v = get(key)) != null) || containsKey(key))
+ ? v
+ : defaultValue;
+ }
+
+ /**
+ * Performs the given action on each entry in this map, in the
+ * order entries are returned by an entry set iterator, until all entries
+ * have been processed or the action throws an {@code Exception}.
+ * Exceptions thrown by the action are relayed to the caller.
+ *
+ * The default implementation should be overridden by implementations if
+ * they can provide a more performant implementation than an iterator-based
+ * one.
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec The default implementation is equivalent to, for this
+ * {@code map}:
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * The default implementation is equivalent to, for this {@code map}:
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * The default implementation is equivalent to, for this {@code
+ * map}:
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * The default implementation is equivalent to, for this {@code map}:
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * The default implementation is equivalent to, for this {@code map}:
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec
+ * The default implementation is equivalent to, for this {@code map}:
+ *
+ * If the function returns {@code null} no mapping is recorded. If
+ * the function itself throws an (unchecked) exception, the
+ * exception is rethrown, and no mapping is recorded. The most
+ * common usage is to construct a new object serving as an initial
+ * mapped value or memoized result, as in:
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to the following
+ * steps for this {@code map}, then returning the current value or
+ * {@code null} if now absent:
+ *
+ * If the function returns {@code null}, the mapping is removed. If the
+ * function itself throws an (unchecked) exception, the exception is
+ * rethrown, and the current mapping is left unchanged.
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the
+ * following steps for this {@code map}, then returning the
+ * current value or {@code null} if now absent:
+ *
+ * If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the following
+ * steps for this {@code map}, then returning the current value or
+ * {@code null} if absent:
+ *
+ * If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ * The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the
+ * following steps for this {@code map}, then returning the
+ * current value or {@code null} if absent:
+ *
+ * Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
@@ -57,6 +57,21 @@
* @param key
to the specified
* value
in this hashtable. Neither the key nor the
@@ -492,21 +515,7 @@
}
}
- modCount++;
- if (count >= threshold) {
- // Rehash the table if the threshold is exceeded
- rehash();
-
- tab = table;
- hash = hash(key);
- index = (hash & 0x7FFFFFFF) % tab.length;
- }
-
- // Creates the new entry.
- @SuppressWarnings("unchecked")
- Entry {@code
+ * for ((Map.Entry
+ *
+ * @param action The action to be performed for each entry
+ * @throws NullPointerException if the specified action is null
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ default void forEach(BiConsumer super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ for (Map.Entry {@code
+ * for ((Map.Entry
+ *
+ * @param function the function to apply to each entry
+ * @throws UnsupportedOperationException if the {@code set} operation
+ * is not supported by this map's entry set iterator.
+ * @throws ClassCastException if the class of a replacement value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if the specified function is null, or the
+ * specified replacement value is null, and this map does not permit null
+ * values
+ * @throws ClassCastException if a replacement value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if function or a replacement value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @throws IllegalArgumentException if some property of a replacement value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ default void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ Objects.requireNonNull(function);
+ for (Map.Entry {@code
+ * if (map.get(key) == null)
+ * return map.put(key, value);
+ * else
+ * return map.get(key);}
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key, or
+ * {@code 1} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (optional)
+ * @throws ConcurrentModificationException if a modification of the map is
+ * detected during insertion of the value.
+ * @since 1.8
+ */
+ default V putIfAbsent(K key, V value) {
+ V v = get(key);
+ if (v == null) {
+ if (put(key, value) != null) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ return v;
+ }
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ * {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.remove(key);
+ * return true;
+ * } else
+ * return false;}
+ *
+ * @param key key with which the specified value is associated
+ * @param value value expected to be associated with the specified key
+ * @return {@code true} if the value was removed
+ * @throws UnsupportedOperationException if the {@code remove} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @since 1.8
+ */
+ default boolean remove(Object key, Object value) {
+ if (!Objects.equals(get(key), value) || !containsKey(key))
+ return false;
+ remove(key);
+ return true;
+ }
+
+ /**
+ * Replaces the entry for the specified key only if currently
+ * mapped to the specified value.
+ *
+ * {@code
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+ * map.put(key, newValue);
+ * return true;
+ * } else
+ * return false;}
+ *
+ * @param key key with which the specified value is associated
+ * @param oldValue value expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of a specified key or value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if a specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of a specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default boolean replace(K key, V oldValue, V newValue) {
+ if (!containsKey(key) || !Objects.equals(get(key), oldValue))
+ return false;
+ put(key, newValue);
+ return true;
+ }
+
+ /**
+ * Replaces the entry for the specified key only if it is
+ * currently mapped to some value.
+ *
+ * {@code
+ * if (map.containsKey(key)) {
+ * return map.put(key, value);
+ * } else
+ * return null;}
+ *
+ * @param key key with which the specified value is associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key, or
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default V replace(K key, V value) {
+ return containsKey(key) ? put(key, value) : null;
+ }
+
+ /**
+ * If the specified key is not already associated with a value (or
+ * is mapped to {@code null}), attempts to compute its value using
+ * the given mapping function and enters it into this map unless
+ * {@code null}.
+ *
+ * {@code
+ * map.computeIfAbsent(key, k -> new Value(f(k)));}
+ *
+ * {@code
+ * if (map.get(key) == null) {
+ * V newValue = mappingFunction.apply(key);
+ * if (newValue != null)
+ * map.putIfAbsent(key, newValue);
+ * }}
+ *
+ * @param key key with which the specified value is to be associated
+ * @param mappingFunction the function to compute a value
+ * @return the current (existing or computed) value associated with
+ * the specified key, or null if the computed value is null
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * mappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V computeIfAbsent(K key, Function super K, ? extends V> mappingFunction) {
+ V v, newValue;
+ return ((v = get(key)) == null &&
+ (newValue = mappingFunction.apply(key)) != null &&
+ (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
+ }
+
+ /**
+ * If the value for the specified key is present and non-null, attempts to
+ * compute a new mapping given the key and its current mapped value.
+ *
+ * {@code
+ * if (map.get(key) != null) {
+ * V oldValue = map.get(key);
+ * V newValue = remappingFunction.apply(key, oldValue);
+ * if (newValue != null)
+ * map.replace(key, oldValue, newValue);
+ * else
+ * map.remove(key, oldValue);
+ * }}
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V computeIfPresent(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ V oldValue;
+ while ((oldValue = get(key)) != null) {
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue))
+ return null;
+ }
+ return oldValue;
+ }
+
+ /**
+ * Attempts to compute a mapping for the specified key and its
+ * current mapped value (or {@code null} if there is no current
+ * mapping). For example, to either create or append a {@code
+ * String msg} to a value mapping:
+ *
+ * {@code
+ * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}
+ * (Method {@link #merge} is often simpler to use for such purposes.)
+ *
+ * {@code
+ * V oldValue = map.get(key);
+ * V newValue = remappingFunction.apply(key, oldValue);
+ * if (oldValue != null )
+ * if (newValue != null)
+ * map.replace(key, oldValue, newValue);
+ * else
+ * map.remove(key, oldValue);
+ * else
+ * if (newValue != null)
+ * map.putIfAbsent(key, newValue);
+ * else
+ * return null;
+ * }
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V compute(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ V oldValue = get(key);
+ for (;;) {
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (oldValue != null) {
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (newValue != null) {
+ if ((oldValue = putIfAbsent(key, newValue)) == null)
+ return newValue;
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ /**
+ * If the specified key is not already associated with a
+ * (non-null) value, associates it with the given value.
+ * Otherwise, replaces the value with the results of the given
+ * remapping function, or removes if {@code null}. This method may
+ * be of use when combining multiple mapped values for a key. For
+ * example, to either create or append a {@code String msg} to a
+ * value mapping:
+ *
+ * {@code
+ * map.merge(key, msg, String::concat)}
+ *
+ * {@code
+ * V oldValue = map.get(key);
+ * V newValue = (oldValue == null) ? value :
+ * remappingFunction.apply(oldValue, value);
+ * if (newValue == null)
+ * map.remove(key, oldValue);
+ * else if (oldValue == null)
+ * map.putIfAbsent(key, newValue);
+ * else
+ * map.replace(key, oldValue, newValue);
+ * }
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value the value to use if absent
+ * @param remappingFunction the function to recompute a value if present
+ * @return the new value associated with the specified key, or null if none
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @since 1.8
+ */
+ default V merge(K key, V value,
+ BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ V oldValue = get(key);
+ for (;;) {
+ if (oldValue != null) {
+ V newValue = remappingFunction.apply(oldValue, value);
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (value == null) {
+ return null;
+ }
+
+ if ((oldValue = putIfAbsent(key, value)) == null) {
+ return value;
+ }
+ }
+ }
+ }
}
diff --git a/src/share/classes/java/util/concurrent/ConcurrentMap.java b/src/share/classes/java/util/concurrent/ConcurrentMap.java
--- a/src/share/classes/java/util/concurrent/ConcurrentMap.java
+++ b/src/share/classes/java/util/concurrent/ConcurrentMap.java
@@ -38,7 +38,7 @@
/**
* A {@link java.util.Map} providing additional atomic
- * putIfAbsent, remove, and replace methods.
+ * {@code putIfAbsent}, {@code remove}, and {@code replace} methods.
*
* {@code
- * if (map.containsKey(key) && map.get(key).equals(value)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.remove(key);
* return true;
* } else
@@ -101,8 +116,8 @@
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
- * @return true if the value was removed
- * @throws UnsupportedOperationException if the remove operation
+ * @return {@code true} if the value was removed
+ * @throws UnsupportedOperationException if the {@code remove} operation
* is not supported by this map
* @throws ClassCastException if the key or value is of an inappropriate
* type for this map
@@ -117,7 +132,7 @@
* Replaces the entry for a key only if currently mapped to a given value.
* This is equivalent to
*
{@code
- * if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) {
* map.put(key, newValue);
* return true;
* } else
@@ -128,8 +143,8 @@
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
- * @return true if the value was replaced
- * @throws UnsupportedOperationException if the put operation
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of a specified key or value
* prevents it from being stored in this map
@@ -154,11 +169,11 @@
* @param key key with which the specified value is associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
- * null if there was no mapping for the key.
- * (A null return can also indicate that the map
- * previously associated null with the key,
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
* if the implementation supports null values.)
- * @throws UnsupportedOperationException if the put operation
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
diff --git a/test/java/util/Map/ToArray.java b/test/java/util/Map/Defaults.java
copy from test/java/util/Map/ToArray.java
copy to test/java/util/Map/Defaults.java
--- a/test/java/util/Map/ToArray.java
+++ b/test/java/util/Map/Defaults.java
@@ -23,165 +23,572 @@
/*
* @test
- * @bug 8008785
- * @summary Ensure toArray() implementations return correct results.
+ * @bug 8010122 8004518
+ * @summary Test Map default methods
* @author Mike Duigou
+ * @run testng Defaults
*/
-import java.util.*;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.function.Supplier;
-public class ToArray {
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+import static org.testng.Assert.fail;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+
+public class Defaults {
+
+ @Test(dataProvider = "Nulls Map