8024688: further split Map and ConcurrentMap defaults eliminating looping from Map defaults, Map.merge fixes and doc fixes.
authormduigou
Wed, 23 Oct 2013 14:32:41 -0700
changeset 21352 0372edc9a995
parent 21351 3509dd1d1cab
child 21353 9792667abbf2
8024688: further split Map and ConcurrentMap defaults eliminating looping from Map defaults, Map.merge fixes and doc fixes. Reviewed-by: psandoz, dholmes
jdk/src/share/classes/java/util/HashMap.java
jdk/src/share/classes/java/util/Map.java
jdk/src/share/classes/java/util/concurrent/ConcurrentMap.java
jdk/test/java/util/Map/Defaults.java
--- a/jdk/src/share/classes/java/util/HashMap.java	Wed Oct 23 20:51:14 2013 +0100
+++ b/jdk/src/share/classes/java/util/HashMap.java	Wed Oct 23 14:32:41 2013 -0700
@@ -915,7 +915,7 @@
             return removeNode(hash(key), key, null, false, true) != null;
         }
         public final Spliterator<K> spliterator() {
-            return new KeySpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
+            return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
         }
         public final void forEach(Consumer<? super K> action) {
             Node<K,V>[] tab;
@@ -959,7 +959,7 @@
         public final Iterator<V> iterator()     { return new ValueIterator(); }
         public final boolean contains(Object o) { return containsValue(o); }
         public final Spliterator<V> spliterator() {
-            return new ValueSpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
+            return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
         }
         public final void forEach(Consumer<? super V> action) {
             Node<K,V>[] tab;
@@ -1022,7 +1022,7 @@
             return false;
         }
         public final Spliterator<Map.Entry<K,V>> spliterator() {
-            return new EntrySpliterator<K,V>(HashMap.this, 0, -1, 0, 0);
+            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
         }
         public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
             Node<K,V>[] tab;
@@ -1042,19 +1042,23 @@
 
     // Overrides of JDK8 Map extension methods
 
+    @Override
     public V getOrDefault(Object key, V defaultValue) {
         Node<K,V> e;
         return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
     }
 
+    @Override
     public V putIfAbsent(K key, V value) {
         return putVal(hash(key), key, value, true, true);
     }
 
+    @Override
     public boolean remove(Object key, Object value) {
         return removeNode(hash(key), key, value, true, true) != null;
     }
 
+    @Override
     public boolean replace(K key, V oldValue, V newValue) {
         Node<K,V> e; V v;
         if ((e = getNode(hash(key), key)) != null &&
@@ -1066,6 +1070,7 @@
         return false;
     }
 
+    @Override
     public V replace(K key, V value) {
         Node<K,V> e;
         if ((e = getNode(hash(key), key)) != null) {
@@ -1077,6 +1082,7 @@
         return null;
     }
 
+    @Override
     public V computeIfAbsent(K key,
                              Function<? super K, ? extends V> mappingFunction) {
         if (mappingFunction == null)
@@ -1150,6 +1156,7 @@
         return null;
     }
 
+    @Override
     public V compute(K key,
                      BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
         if (remappingFunction == null)
@@ -1202,6 +1209,7 @@
         return v;
     }
 
+    @Override
     public V merge(K key, V value,
                    BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
         if (remappingFunction == null)
@@ -1230,7 +1238,11 @@
             }
         }
         if (old != null) {
-            V v = remappingFunction.apply(old.value, value);
+            V v;
+            if (old.value != null)
+                v = remappingFunction.apply(old.value, value);
+            else
+                v = value;
             if (v != null) {
                 old.value = v;
                 afterNodeAccess(old);
@@ -1254,6 +1266,7 @@
         return value;
     }
 
+    @Override
     public void forEach(BiConsumer<? super K, ? super V> action) {
         Node<K,V>[] tab;
         if (action == null)
@@ -1269,6 +1282,7 @@
         }
     }
 
+    @Override
     public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
         Node<K,V>[] tab;
         if (function == null)
@@ -1295,6 +1309,7 @@
      * @return a shallow copy of this map
      */
     @SuppressWarnings("unchecked")
+    @Override
     public Object clone() {
         HashMap<K,V> result;
         try {
@@ -1496,7 +1511,7 @@
         public KeySpliterator<K,V> trySplit() {
             int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
             return (lo >= mid || current != null) ? null :
-                new KeySpliterator<K,V>(map, lo, index = mid, est >>>= 1,
+                new KeySpliterator<>(map, lo, index = mid, est >>>= 1,
                                         expectedModCount);
         }
 
@@ -1568,7 +1583,7 @@
         public ValueSpliterator<K,V> trySplit() {
             int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
             return (lo >= mid || current != null) ? null :
-                new ValueSpliterator<K,V>(map, lo, index = mid, est >>>= 1,
+                new ValueSpliterator<>(map, lo, index = mid, est >>>= 1,
                                           expectedModCount);
         }
 
@@ -1639,7 +1654,7 @@
         public EntrySpliterator<K,V> trySplit() {
             int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
             return (lo >= mid || current != null) ? null :
-                new EntrySpliterator<K,V>(map, lo, index = mid, est >>>= 1,
+                new EntrySpliterator<>(map, lo, index = mid, est >>>= 1,
                                           expectedModCount);
         }
 
@@ -1714,22 +1729,22 @@
 
     // Create a regular (non-tree) node
     Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
-        return new Node<K,V>(hash, key, value, next);
+        return new Node<>(hash, key, value, next);
     }
 
     // For conversion from TreeNodes to plain nodes
     Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
-        return new Node<K,V>(p.hash, p.key, p.value, next);
+        return new Node<>(p.hash, p.key, p.value, next);
     }
 
     // Create a tree bin node
     TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
-        return new TreeNode<K,V>(hash, key, value, next);
+        return new TreeNode<>(hash, key, value, next);
     }
 
     // For treeifyBin
     TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
-        return new TreeNode<K,V>(p.hash, p.key, p.value, next);
+        return new TreeNode<>(p.hash, p.key, p.value, next);
     }
 
     /**
--- a/jdk/src/share/classes/java/util/Map.java	Wed Oct 23 20:51:14 2013 +0100
+++ b/jdk/src/share/classes/java/util/Map.java	Wed Oct 23 14:32:41 2013 -0700
@@ -565,7 +565,8 @@
      * Returns the value to which the specified key is mapped, or
      * {@code defaultValue} if this map contains no mapping for the key.
      *
-     * <p>The default implementation makes no guarantees about synchronization
+     * @implSpec
+     * 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.
@@ -596,22 +597,18 @@
      * the order of entry set iteration (if an iteration order is specified.)
      * Exceptions thrown by the action are relayed to the caller.
      *
-     * <p>The default implementation should be overridden by implementations if
-     * they can provide a more performant implementation than an iterator-based
-     * one.
-     *
-     * <p>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}:
+     * @implSpec
+     * The default implementation is equivalent to, for this {@code map}:
      * <pre> {@code
      * for ((Map.Entry<K, V> entry : map.entrySet())
      *     action.accept(entry.getKey(), entry.getValue());
      * }</pre>
      *
+     * 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 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
@@ -640,11 +637,6 @@
      * function throws an exception.  Exceptions thrown by the function are
      * relayed to the caller.
      *
-     * <p>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
      * <p>The default implementation is equivalent to, for this {@code map}:
      * <pre> {@code
@@ -652,6 +644,11 @@
      *     entry.setValue(function.apply(entry.getKey(), entry.getValue()));
      * }</pre>
      *
+     * <p>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 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.
@@ -703,22 +700,23 @@
      * to {@code null}) associates it with the given value and returns
      * {@code null}, else returns the current value.
      *
-     * <p>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}:
      *
      * <pre> {@code
-     * if (map.get(key) == null)
-     *     return map.put(key, value);
-     * else
-     *     return map.get(key);
+     * V v = map.get(key);
+     * if (v == null)
+     *     v = map.put(key, value);
+     *
+     * return v;
      * }</pre>
      *
+     * <p>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 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
@@ -738,16 +736,12 @@
      * @throws IllegalArgumentException if some property of the specified key
      *         or value prevents it from being stored in this map
      *         (<a href="Collection.html#optional-restrictions">optional</a>)
-     * @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();
-            }
+            v = put(key, value);
         }
 
         return v;
@@ -757,11 +751,6 @@
      * Removes the entry for the specified key only if it is currently
      * mapped to the specified value.
      *
-     * <p>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}:
      *
@@ -773,6 +762,11 @@
      *     return false;
      * }</pre>
      *
+     * <p>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 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
@@ -801,11 +795,6 @@
      * Replaces the entry for the specified key only if currently
      * mapped to the specified value.
      *
-     * <p>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}:
      *
@@ -821,6 +810,11 @@
      * for maps that do not support null values if oldValue is null unless
      * newValue is also null.
      *
+     * <p>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 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
@@ -853,11 +847,6 @@
      * Replaces the entry for the specified key only if it is
      * currently mapped to some value.
      *
-     * <p>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}:
      *
@@ -868,6 +857,11 @@
      *     return null;
      * }</pre>
      *
+     * <p>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 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
@@ -888,14 +882,17 @@
      * @since 1.8
      */
     default V replace(K key, V value) {
-        return containsKey(key) ? put(key, value) : null;
+        V curValue;
+        if (((curValue = get(key)) != null) || containsKey(key)) {
+            curValue = put(key, value);
+        }
+        return curValue;
     }
 
     /**
-     * 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}.
+     * 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}.
      *
      * <p>If the function returns {@code null} no mapping is recorded. If
      * the function itself throws an (unchecked) exception, the
@@ -907,35 +904,42 @@
      * map.computeIfAbsent(key, k -> new Value(f(k)));
      * }</pre>
      *
+     * <p>Or to implement a multi-value map, {@code Map<K,Collection<V>>},
+     * supporting multiple values per key:
+     *
+     * <pre> {@code
+     * map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
+     * }</pre>
+     *
+     *
+     * @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:
+     *
+     * <pre> {@code
+     * if (map.get(key) == null) {
+     *     V newValue = mappingFunction.apply(key);
+     *     if (newValue != null)
+     *         map.put(key, newValue);
+     * }
+     * }</pre>
+     *
      * <p>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:
-     *
-     * <pre> {@code
-     * if (map.get(key) == null) {
-     *     V newValue = mappingFunction.apply(key);
-     *     if (newValue != null)
-     *         map.putIfAbsent(key, newValue);
-     * }
-     * }</pre>
+     * present.
      *
      * @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
+     *         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
      *         (<a href="Collection.html#optional-restrictions">optional</a>)
@@ -947,10 +951,16 @@
     default V computeIfAbsent(K key,
             Function<? super K, ? extends V> mappingFunction) {
         Objects.requireNonNull(mappingFunction);
-        V v, newValue;
-        return ((v = get(key)) == null &&
-                (newValue = mappingFunction.apply(key)) != null &&
-                (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
+        V v;
+        if ((v = get(key)) == null) {
+            V newValue;
+            if ((newValue = mappingFunction.apply(key)) != null) {
+                put(key, newValue);
+                return newValue;
+            }
+        }
+
+        return v;
     }
 
     /**
@@ -960,6 +970,22 @@
      * <p>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.
+    *
+     * @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:
+     *
+     * <pre> {@code
+     * if (map.get(key) != null) {
+     *     V oldValue = map.get(key);
+     *     V newValue = remappingFunction.apply(key, oldValue);
+     *     if (newValue != null)
+     *         map.put(key, newValue);
+     *     else
+     *         map.remove(key);
+     * }
+     * }</pre>
      *
      * <p>The default implementation makes no guarantees about synchronization
      * or atomicity properties of this method. Any implementation providing
@@ -967,27 +993,7 @@
      * 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:
-     *
-     * <pre> {@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);
-     * }
-     * }</pre>
-     *
-     * In concurrent contexts, the default implementation may retry
-     * these steps when multiple threads attempt updates.
+     * present.
      *
      * @param key key with which the specified value is to be associated
      * @param remappingFunction the function to compute a value
@@ -1007,22 +1013,25 @@
             BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
         Objects.requireNonNull(remappingFunction);
         V oldValue;
-        while ((oldValue = get(key)) != null) {
+        if ((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))
+                put(key, newValue);
+                return newValue;
+            } else {
+                remove(key);
                 return null;
+            }
+        } else {
+            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:
+     * 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:
      *
      * <pre> {@code
      * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}</pre>
@@ -1033,15 +1042,6 @@
      * (unchecked) exception, the exception is rethrown, and the current mapping
      * is left unchanged.
      *
-     * <p>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
@@ -1052,19 +1052,24 @@
      * V newValue = remappingFunction.apply(key, oldValue);
      * if (oldValue != null ) {
      *    if (newValue != null)
-     *       map.replace(key, oldValue, newValue);
+     *       map.put(key, newValue);
      *    else
-     *       map.remove(key, oldValue);
+     *       map.remove(key);
      * } else {
      *    if (newValue != null)
-     *       map.putIfAbsent(key, newValue);
+     *       map.put(key, newValue);
      *    else
      *       return null;
      * }
      * }</pre>
      *
-     * In concurrent contexts, the default implementation may retry
-     * these steps when multiple threads attempt updates.
+     * <p>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.
      *
      * @param key key with which the specified value is to be associated
      * @param remappingFunction the function to compute a value
@@ -1084,44 +1089,22 @@
             BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
         Objects.requireNonNull(remappingFunction);
         V oldValue = get(key);
-        for (;;) {
-            V newValue = remappingFunction.apply(key, oldValue);
-            if (newValue == null) {
-                // delete mapping
-                if(oldValue != null || containsKey(key)) {
-                    // something to remove
-                    if (remove(key, oldValue)) {
-                        // removed the old value as expected
-                        return null;
-                    }
 
-                    // some other value replaced old value. try again.
-                    oldValue = get(key);
-                } else {
-                    // nothing to do. Leave things as they were.
-                    return null;
-                }
+        V newValue = remappingFunction.apply(key, oldValue);
+        if (newValue == null) {
+            // delete mapping
+            if (oldValue != null || containsKey(key)) {
+                // something to remove
+                remove(key);
+                return null;
             } else {
-                // add or replace old mapping
-                if (oldValue != null) {
-                    // replace
-                    if (replace(key, oldValue, newValue)) {
-                        // replaced as expected.
-                        return newValue;
-                    }
-
-                    // some other value replaced old value. try again.
-                    oldValue = get(key);
-                } else {
-                    // add (replace if oldValue was null)
-                    if ((oldValue = putIfAbsent(key, newValue)) == null) {
-                        // replaced
-                        return newValue;
-                    }
-
-                    // some other value replaced old value. try again.
-                }
+                // nothing to do. Leave things as they were.
+                return null;
             }
+        } else {
+            // add or replace old mapping
+            put(key, newValue);
+            return newValue;
         }
     }
 
@@ -1143,15 +1126,6 @@
      * (unchecked) exception, the exception is rethrown, and the current mapping
      * is left unchanged.
      *
-     * <p>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
@@ -1162,15 +1136,20 @@
      * V newValue = (oldValue == null) ? value :
      *              remappingFunction.apply(oldValue, value);
      * if (newValue == null)
-     *     map.remove(key, oldValue);
+     *     map.remove(key);
      * else if (oldValue == null)
-     *     map.putIfAbsent(key, newValue);
+     *     map.remove(key);
      * else
-     *     map.replace(key, oldValue, newValue);
+     *     map.put(key, newValue);
      * }</pre>
      *
-     * In concurrent contexts, the default implementation may retry
-     * these steps when multiple threads attempt updates.
+     * <p>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.
      *
      * @param key key with which the specified value is to be associated
      * @param value the value to use if absent
@@ -1183,32 +1162,30 @@
      *         prevents it from being stored in this map
      *         (<a href="Collection.html#optional-restrictions">optional</a>)
      * @throws NullPointerException if the specified key is null and
-     *         this map does not support null keys, or the
-     *         remappingFunction is null
+     *         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) {
         Objects.requireNonNull(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);
+        if (oldValue != null) {
+            V newValue = remappingFunction.apply(oldValue, value);
+            if (newValue != null) {
+                put(key, newValue);
+                return newValue;
             } else {
-                if (value == null) {
-                    return null;
-                }
-
-                if ((oldValue = putIfAbsent(key, value)) == null) {
-                    return value;
-                }
+                remove(key);
+                return null;
+            }
+        } else {
+            if (value == null) {
+                remove(key);
+                return null;
+            } else {
+                put(key, value);
+                return value;
             }
         }
     }
--- a/jdk/src/share/classes/java/util/concurrent/ConcurrentMap.java	Wed Oct 23 20:51:14 2013 +0100
+++ b/jdk/src/share/classes/java/util/concurrent/ConcurrentMap.java	Wed Oct 23 14:32:41 2013 -0700
@@ -36,7 +36,9 @@
 package java.util.concurrent;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /**
  * A {@link java.util.Map} providing thread safety and atomicity
@@ -64,9 +66,13 @@
      * {@inheritDoc}
      *
      * @implNote This implementation assumes that the ConcurrentMap cannot
-     * contain null values and get() returning null unambiguously means the key
-     * is absent. Implementations which support null values must override this
-     * default implementation.
+     * contain null values and {@code get()} returning null unambiguously means
+     * the key is absent. Implementations which support null values
+     * <strong>must</strong> override this default implementation.
+     *
+     * @throws ClassCastException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
      */
     @Override
     default V getOrDefault(Object key, V defaultValue) {
@@ -74,6 +80,41 @@
         return ((v = get(key)) != null) ? v : defaultValue;
     }
 
+   /**
+     * {@inheritDoc}
+     *
+     * @implSpec The default implementation is equivalent to, for this
+     * {@code map}:
+     * <pre> {@code
+     * for ((Map.Entry<K, V> entry : map.entrySet())
+     *     action.accept(entry.getKey(), entry.getValue());
+     * }</pre>
+     *
+     * @implNote The default implementation assumes that
+     * {@code IllegalStateException} thrown by {@code getKey()} or
+     * {@code getValue()} indicates that the entry has been removed and cannot
+     * be processed. Operation continues for subsequent entries.
+     *
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
+     */
+    @Override
+    default void forEach(BiConsumer<? super K, ? super V> action) {
+        Objects.requireNonNull(action);
+        for (Map.Entry<K, V> entry : entrySet()) {
+            K k;
+            V v;
+            try {
+                k = entry.getKey();
+                v = entry.getValue();
+            } catch(IllegalStateException ise) {
+                // this usually means the entry is no longer in the map.
+                continue;
+            }
+            action.accept(k, v);
+        }
+    }
+
     /**
      * If the specified key is not already associated
      * with a value, associate it with the given value.
@@ -82,10 +123,14 @@
      * if (!map.containsKey(key))
      *   return map.put(key, value);
      * else
-     *   return map.get(key);}</pre>
+     *   return map.get(key);
+     * }</pre>
      *
      * except that the action is performed atomically.
      *
+     * @implNote This implementation intentionally re-abstracts the
+     * inappropriate default provided in {@code Map}.
+     *
      * @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
@@ -102,7 +147,7 @@
      * @throws IllegalArgumentException if some property of the specified key
      *         or value prevents it from being stored in this map
      */
-    V putIfAbsent(K key, V value);
+     V putIfAbsent(K key, V value);
 
     /**
      * Removes the entry for a key only if currently mapped to a given value.
@@ -112,10 +157,14 @@
      *   map.remove(key);
      *   return true;
      * } else
-     *   return false;}</pre>
+     *   return false;
+     * }</pre>
      *
      * except that the action is performed atomically.
      *
+     * @implNote This implementation intentionally re-abstracts the
+     * inappropriate default provided in {@code Map}.
+     *
      * @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
@@ -138,10 +187,14 @@
      *   map.put(key, newValue);
      *   return true;
      * } else
-     *   return false;}</pre>
+     *   return false;
+     * }</pre>
      *
      * except that the action is performed atomically.
      *
+     * @implNote This implementation intentionally re-abstracts the
+     * inappropriate default provided in {@code Map}.
+     *
      * @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
@@ -164,10 +217,14 @@
      * if (map.containsKey(key)) {
      *   return map.put(key, value);
      * } else
-     *   return null;}</pre>
+     *   return null;
+     * }</pre>
      *
      * except that the action is performed atomically.
      *
+     * @implNote This implementation intentionally re-abstracts the
+     * inappropriate default provided in {@code Map}.
+     *
      * @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
@@ -189,10 +246,30 @@
     /**
      * {@inheritDoc}
      *
-     * @implNote This implementation assumes that the ConcurrentMap cannot
-     * contain null values and get() returning null unambiguously means the key
-     * is absent. Implementations which support null values
-     * <strong>must</strong> override this default implementation.
+     * @implSpec
+     * <p>The default implementation is equivalent to, for this {@code map}:
+     * <pre> {@code
+     * for ((Map.Entry<K, V> entry : map.entrySet())
+     *     do {
+     *        K k = entry.getKey();
+     *        V v = entry.getValue();
+     *     } while(!replace(k, v, function.apply(k, v)));
+     * }</pre>
+     *
+     * The default implementation may retry these steps when multiple
+     * threads attempt updates including potentially calling the function
+     * repeatedly for a given key.
+     *
+     * <p>This implementation assumes that the ConcurrentMap cannot contain null
+     * values and {@code get()} returning null unambiguously means the key is
+     * absent. Implementations which support null values <strong>must</strong>
+     * override this default implementation.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @throws ClassCastException {@inheritDoc}
+     * @throws IllegalArgumentException {@inheritDoc}
+     * @since 1.8
      */
     @Override
     default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
@@ -200,11 +277,243 @@
         forEach((k,v) -> {
             while(!replace(k, v, function.apply(k, v))) {
                 // v changed or k is gone
-                if( (v = get(k)) == null) {
+                if ( (v = get(k)) == null) {
                     // k is no longer in the map.
                     break;
                 }
             }
         });
     }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @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:
+     *
+     * <pre> {@code
+     * if (map.get(key) == null) {
+     *     V newValue = mappingFunction.apply(key);
+     *     if (newValue != null)
+     *         return map.putIfAbsent(key, newValue);
+     * }
+     * }</pre>
+     *
+     * The default implementation may retry these steps when multiple
+     * threads attempt updates including potentially calling the mapping
+     * function multiple times.
+     *
+     * <p>This implementation assumes that the ConcurrentMap cannot contain null
+     * values and {@code get()} returning null unambiguously means the key is
+     * absent. Implementations which support null values <strong>must</strong>
+     * override this default implementation.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws ClassCastException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
+     */
+    @Override
+    default V computeIfAbsent(K key,
+            Function<? super K, ? extends V> mappingFunction) {
+        Objects.requireNonNull(mappingFunction);
+        V v, newValue;
+        return ((v = get(key)) == null &&
+                (newValue = mappingFunction.apply(key)) != null &&
+                (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @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. :
+     *
+     * <pre> {@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);
+     * }
+     * }</pre>
+     *
+     * The default implementation may retry these steps when multiple threads
+     * attempt updates including potentially calling the remapping function
+     * multiple times.
+     *
+     * <p>This implementation assumes that the ConcurrentMap cannot contain null
+     * values and {@code get()} returning null unambiguously means the key is
+     * absent. Implementations which support null values <strong>must</strong>
+     * override this default implementation.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws ClassCastException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
+     */
+    @Override
+    default V computeIfPresent(K key,
+            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(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;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @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:
+     *
+     * <pre> {@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;
+     * }
+     * }</pre>
+     *
+     * The default implementation may retry these steps when multiple
+     * threads attempt updates including potentially calling the remapping
+     * function multiple times.
+     *
+     * <p>This implementation assumes that the ConcurrentMap cannot contain null
+     * values and {@code get()} returning null unambiguously means the key is
+     * absent. Implementations which support null values <strong>must</strong>
+     * override this default implementation.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws ClassCastException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
+     */
+    @Override
+    default V compute(K key,
+            BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+        V oldValue = get(key);
+        for(;;) {
+            V newValue = remappingFunction.apply(key, oldValue);
+            if (newValue == null) {
+                // delete mapping
+                if (oldValue != null || containsKey(key)) {
+                    // something to remove
+                    if (remove(key, oldValue)) {
+                        // removed the old value as expected
+                        return null;
+                    }
+
+                    // some other value replaced old value. try again.
+                    oldValue = get(key);
+                } else {
+                    // nothing to do. Leave things as they were.
+                    return null;
+                }
+            } else {
+                // add or replace old mapping
+                if (oldValue != null) {
+                    // replace
+                    if (replace(key, oldValue, newValue)) {
+                        // replaced as expected.
+                        return newValue;
+                    }
+
+                    // some other value replaced old value. try again.
+                    oldValue = get(key);
+                } else {
+                    // add (replace if oldValue was null)
+                    if ((oldValue = putIfAbsent(key, newValue)) == null) {
+                        // replaced
+                        return newValue;
+                    }
+
+                    // some other value replaced old value. try again.
+                }
+            }
+        }
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * @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:
+     *
+     * <pre> {@code
+     * V oldValue = map.get(key);
+     * V newValue = (oldValue == null) ? value :
+     *              remappingFunction.apply(oldValue, value);
+     * if (newValue == null)
+     *     map.remove(key);
+     * else if (oldValue == null)
+     *     map.remove(key);
+     * else
+     *     map.put(key, newValue);
+     * }</pre>
+     *
+     * <p>The default implementation may retry these steps when multiple
+     * threads attempt updates including potentially calling the remapping
+     * function multiple times.
+     *
+     * <p>This implementation assumes that the ConcurrentMap cannot contain null
+     * values and {@code get()} returning null unambiguously means the key is
+     * absent. Implementations which support null values <strong>must</strong>
+     * override this default implementation.
+     *
+     * @throws UnsupportedOperationException {@inheritDoc}
+     * @throws ClassCastException {@inheritDoc}
+     * @throws NullPointerException {@inheritDoc}
+     * @since 1.8
+     */
+    @Override
+    default V merge(K key, V value,
+            BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+        Objects.requireNonNull(value);
+        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 ((oldValue = putIfAbsent(key, value)) == null) {
+                    return value;
+                }
+            }
+        }
+    }
 }
--- a/jdk/test/java/util/Map/Defaults.java	Wed Oct 23 20:51:14 2013 +0100
+++ b/jdk/test/java/util/Map/Defaults.java	Wed Oct 23 14:32:41 2013 -0700
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8010122 8004518 8024331
+ * @bug 8010122 8004518 8024331 8024688
  * @summary Test Map default methods
  * @author Mike Duigou
  * @run testng Defaults
@@ -36,8 +36,8 @@
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.HashSet;
-import java.util.Hashtable;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -288,19 +288,11 @@
         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfAbsentNPEHashMap() {
-        Object value = new HashMap().computeIfAbsent(KEYS[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfAbsentNPEHashtable() {
-        Object value = new Hashtable().computeIfAbsent(KEYS[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfAbsentNPETreeMap() {
-        Object value = new TreeMap().computeIfAbsent(KEYS[1], null);
+    @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
+    public void testComputeIfAbsentNullFunction(String description, Map<IntegerEnum, String> map) {
+        assertThrows( () -> { map.computeIfAbsent(KEYS[1], null);},
+                NullPointerException.class,
+                "Should throw NPE");
     }
 
     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
@@ -343,22 +335,14 @@
         assertSame(map.get(EXTRA_KEY), null);
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfPresentNPEHashMap() {
-        Object value = new HashMap().computeIfPresent(KEYS[1], null);
+    @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
+    public void testComputeIfPresentNullFunction(String description, Map<IntegerEnum, String> map) {
+        assertThrows( () -> { map.computeIfPresent(KEYS[1], null);},
+                NullPointerException.class,
+                "Should throw NPE");
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfPresentNPEHashtable() {
-        Object value = new Hashtable().computeIfPresent(KEYS[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeIfPresentNPETreeMap() {
-        Object value = new TreeMap().computeIfPresent(KEYS[1], null);
-    }
-
-    @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
+     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
     public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
         assertTrue(map.containsKey(null), "null key absent");
         assertNull(map.get(null), "value not null");
@@ -444,78 +428,86 @@
         assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeNPEHashMap() {
-        Object value = new HashMap().compute(KEYS[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeNPEHashtable() {
-        Object value = new Hashtable().compute(KEYS[1], null);
+    @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
+    public void testComputeNullFunction(String description, Map<IntegerEnum, String> map) {
+        assertThrows( () -> { map.compute(KEYS[1], null);},
+                NullPointerException.class,
+                "Should throw NPE");
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testComputeNPETreeMap() {
-        Object value = new TreeMap().compute(KEYS[1], null);
-    }
+    @Test(dataProvider = "MergeCases")
+    private void testMerge(String description, Map<IntegerEnum, String> map, Merging.Value oldValue, Merging.Value newValue, Merging.Merger merger, Merging.Value put, Merging.Value result) {
+            // add and check initial conditions.
+            switch(oldValue) {
+                case ABSENT :
+                    map.remove(EXTRA_KEY);
+                    assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
+                    break;
+                case NULL :
+                    map.put(EXTRA_KEY, null);
+                    assertTrue(map.containsKey(EXTRA_KEY), "key absent");
+                    assertNull(map.get(EXTRA_KEY), "wrong value");
+                    break;
+                case OLDVALUE :
+                    map.put(EXTRA_KEY, VALUES[1]);
+                    assertTrue(map.containsKey(EXTRA_KEY), "key absent");
+                    assertSame(map.get(EXTRA_KEY), VALUES[1], "wrong value");
+                    break;
+                default:
+                    fail("unexpected old value");
+            }
+
+            String returned = map.merge(EXTRA_KEY,
+                newValue == Merging.Value.NULL ? (String) null : VALUES[2],
+                merger
+                );
+
+            // check result
 
-    @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=withNull values=withNull")
-    public void testMergeNulls(String description, Map<IntegerEnum, String> map) {
-        assertTrue(map.containsKey(null), "null key absent");
-        assertNull(map.get(null), "value not null");
-        assertSame(map.merge(null, EXTRA_VALUE, (v, vv) -> {
-            assertNull(v);
-            assertSame(vv, EXTRA_VALUE);
-            return vv;
-        }), EXTRA_VALUE, description);
-        assertTrue(map.containsKey(null));
-        assertSame(map.get(null), EXTRA_VALUE, description);
+            switch(result) {
+                case NULL :
+                    assertNull(returned, "wrong value");
+                    break;
+                case NEWVALUE :
+                    assertSame(returned, VALUES[2], "wrong value");
+                    break;
+                case RESULT :
+                    assertSame(returned, VALUES[3], "wrong value");
+                    break;
+                default:
+                    fail("unexpected new value");
+            }
+
+            // check map
+            switch(put) {
+                case ABSENT :
+                    assertFalse(map.containsKey(EXTRA_KEY), "key not absent");
+                    break;
+                case NULL :
+                    assertTrue(map.containsKey(EXTRA_KEY), "key absent");
+                    assertNull(map.get(EXTRA_KEY), "wrong value");
+                    break;
+                case NEWVALUE :
+                    assertTrue(map.containsKey(EXTRA_KEY), "key absent");
+                    assertSame(map.get(EXTRA_KEY), VALUES[2], "wrong value");
+                    break;
+                case RESULT :
+                    assertTrue(map.containsKey(EXTRA_KEY), "key absent");
+                    assertSame(map.get(EXTRA_KEY), VALUES[3], "wrong value");
+                    break;
+                default:
+                    fail("unexpected new value");
+            }
     }
 
     @Test(dataProvider = "Map<IntegerEnum,String> rw=true keys=all values=all")
-    public void testMerge(String description, Map<IntegerEnum, String> map) {
-        assertTrue(map.containsKey(KEYS[1]));
-        Object value = map.get(KEYS[1]);
-        assertTrue(null == value || value == VALUES[1], description + String.valueOf(value));
-        assertSame(map.merge(KEYS[1], EXTRA_VALUE, (v, vv) -> {
-            assertSame(v, value);
-            assertSame(vv, EXTRA_VALUE);
-            return vv;
-        }), EXTRA_VALUE, description);
-        assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
-        assertNull(map.merge(KEYS[1], EXTRA_VALUE, (v, vv) -> {
-            assertSame(v, EXTRA_VALUE);
-            assertSame(vv, EXTRA_VALUE);
-            return null;
-        }), description);
-        assertFalse(map.containsKey(KEYS[1]));
-
-        assertFalse(map.containsKey(EXTRA_KEY));
-        assertSame(map.merge(EXTRA_KEY, EXTRA_VALUE, (v, vv) -> {
-            assertNull(v);
-            assertSame(vv, EXTRA_VALUE);
-            return EXTRA_VALUE;
-        }), EXTRA_VALUE);
-        assertTrue(map.containsKey(EXTRA_KEY));
-        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+    public void testMergeNullMerger(String description, Map<IntegerEnum, String> map) {
+        assertThrows( () -> { map.merge(KEYS[1], VALUES[1], null);},
+                NullPointerException.class,
+                "Should throw NPE");
     }
 
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testMergeNPEHashMap() {
-        Object value = new HashMap().merge(KEYS[1], VALUES[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testMergeNPEHashtable() {
-        Object value = new Hashtable().merge(KEYS[1], VALUES[1], null);
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void testMergeNPETreeMap() {
-        Object value = new TreeMap().merge(KEYS[1], VALUES[1], null);
-    }
-
-    enum IntegerEnum {
+    public enum IntegerEnum {
 
         e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
         e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
@@ -715,6 +707,89 @@
         return result;
     }
 
+    static class Merging {
+        public enum Value {
+            ABSENT,
+            NULL,
+            OLDVALUE,
+            NEWVALUE,
+            RESULT
+        }
+
+        public enum Merger implements BiFunction<String,String,String> {
+            UNUSED {
+                public String apply(String oldValue, String newValue) {
+                    fail("should not be called");
+                    return null;
+                }
+            },
+            NULL {
+                public String apply(String oldValue, String newValue) {
+                    return null;
+                }
+            },
+            RESULT {
+                public String apply(String oldValue, String newValue) {
+                    return VALUES[3];
+                }
+            },
+        }
+    }
+
+    @DataProvider(name = "MergeCases", parallel = true)
+    public Iterator<Object[]> mergeCasesProvider() {
+        Collection<Object[]> cases = new ArrayList<>();
+
+        cases.addAll(makeMergeTestCases());
+        cases.addAll(makeMergeNullValueTestCases());
+
+        return cases.iterator();
+    }
+
+    static Collection<Object[]> makeMergeTestCases() {
+        Collection<Object[]> cases = new ArrayList<>();
+
+        for( Object[] mapParams : makeAllRWMaps() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
+        }
+
+        for( Object[] mapParams : makeAllRWMaps() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
+        }
+
+        for( Object[] mapParams : makeAllRWMaps() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NEWVALUE, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
+        }
+
+        return cases;
+    }
+
+    static Collection<Object[]> makeMergeNullValueTestCases() {
+        Collection<Object[]> cases = new ArrayList<>();
+
+        for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.NULL, Merging.Value.ABSENT, Merging.Value.NULL });
+        }
+
+        for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.OLDVALUE, Merging.Value.NULL, Merging.Merger.RESULT, Merging.Value.RESULT, Merging.Value.RESULT });
+        }
+
+        for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.ABSENT, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
+        }
+
+        for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NULL, Merging.Merger.UNUSED, Merging.Value.ABSENT, Merging.Value.NULL });
+        }
+
+        for( Object[] mapParams : makeAllRWMapsWithNulls() ) {
+            cases.add(new Object[] { mapParams[0], mapParams[1], Merging.Value.NULL, Merging.Value.NEWVALUE, Merging.Merger.UNUSED, Merging.Value.NEWVALUE, Merging.Value.NEWVALUE });
+        }
+
+        return cases;
+    }
+
     public interface Thrower<T extends Throwable> {
 
         public void run() throws T;