changeset 8194:e4e9f6455f3c

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 <dl@cs.oswego.edu>, Henry Jen <henry.jen@oracle.com>, Akhil Arora <akhil.arora@oracle.com>, Peter Levart <peter.levart@gmail.com>, Mike Duigou <mike.duigou@oracle.com>
author mduigou
date Tue, 16 Apr 2013 11:17:19 -0700
parents 6135c60e77e5
children 10fd3b4a77ae
files src/share/classes/java/util/Collections.java src/share/classes/java/util/HashMap.java src/share/classes/java/util/Hashtable.java src/share/classes/java/util/Map.java src/share/classes/java/util/concurrent/ConcurrentMap.java test/java/util/Map/Defaults.java
diffstat 6 files changed, 2097 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/Collections.java	Tue Apr 16 13:26:30 2013 +0100
+++ b/src/share/classes/java/util/Collections.java	Tue Apr 16 11:17:19 2013 -0700
@@ -28,6 +28,9 @@
 import java.io.ObjectOutputStream;
 import java.io.IOException;
 import java.lang.reflect.Array;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 
 /**
  * This class consists exclusively of static methods that operate on or return
@@ -264,8 +267,7 @@
     }
 
     private static <T>
-    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key)
-    {
+    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
         int low = 0;
         int high = list.size()-1;
 
@@ -441,21 +443,21 @@
     /**
      * Randomly permutes the specified list using a default source of
      * randomness.  All permutations occur with approximately equal
-     * likelihood.<p>
+     * likelihood.
      *
-     * The hedge "approximately" is used in the foregoing description because
+     * <p>The hedge "approximately" is used in the foregoing description because
      * default source of randomness is only approximately an unbiased source
      * of independently chosen bits. If it were a perfect source of randomly
      * chosen bits, then the algorithm would choose permutations with perfect
-     * uniformity.<p>
+     * uniformity.
      *
-     * This implementation traverses the list backwards, from the last element
-     * up to the second, repeatedly swapping a randomly selected element into
-     * the "current position".  Elements are randomly selected from the
+     * <p>This implementation traverses the list backwards, from the last
+     * element up to the second, repeatedly swapping a randomly selected element
+     * into the "current position".  Elements are randomly selected from the
      * portion of the list that runs from the first element to the current
-     * position, inclusive.<p>
+     * position, inclusive.
      *
-     * This method runs in linear time.  If the specified list does not
+     * <p>This method runs in linear time.  If the specified list does not
      * implement the {@link RandomAccess} interface and is large, this
      * implementation dumps the specified list into an array before shuffling
      * it, and dumps the shuffled array back into the list.  This avoids the
@@ -469,9 +471,10 @@
     public static void shuffle(List<?> list) {
         Random rnd = r;
         if (rnd == null)
-            r = rnd = new Random();
+            r = rnd = new Random(); // harmless race.
         shuffle(list, rnd);
     }
+
     private static Random r;
 
     /**
@@ -1391,6 +1394,67 @@
         public int hashCode()           {return m.hashCode();}
         public String toString()        {return m.toString();}
 
+        // Override default methods in Map
+        @Override
+        @SuppressWarnings("unchecked")
+        public V getOrDefault(Object k, V defaultValue) {
+            // Safe cast as we don't change the value
+            return ((Map<K, V>)m).getOrDefault(k, defaultValue);
+        }
+
+        @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) {
+            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();
+        }
+
         /**
          * We need this class in addition to UnmodifiableSet as
          * Map.Entries themselves permit modification of the backing Map
@@ -1590,9 +1654,9 @@
      * </pre>
      * Failure to follow this advice may result in non-deterministic behavior.
      *
-     * <p>The returned collection does <i>not</i> pass the <tt>hashCode</tt>
-     * and <tt>equals</tt> operations through to the backing collection, but
-     * relies on <tt>Object</tt>'s equals and hashCode methods.  This is
+     * <p>The returned collection does <i>not</i> 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.<p>
      *
@@ -2107,6 +2171,57 @@
         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();}
         }
@@ -2326,6 +2441,8 @@
         }
 
         public Iterator<E> iterator() {
+            // JDK-6363904 - unwrapped iterator could be typecast to
+            // ListIterator with unsafe set()
             final Iterator<E> it = c.iterator();
             return new Iterator<E>() {
                 public boolean hasNext() { return it.hasNext(); }
@@ -2652,7 +2769,7 @@
 
         public List<E> subList(int fromIndex, int toIndex) {
             return new CheckedRandomAccessList<>(
-                list.subList(fromIndex, toIndex), type);
+                    list.subList(fromIndex, toIndex), type);
         }
     }
 
@@ -2717,14 +2834,24 @@
                 throw new ClassCastException(badValueMsg(value));
         }
 
+        private BiFunction<? super K, ? super V, ? extends V> typeCheck(
+                BiFunction<? super K, ? super V, ? extends V> func) {
+            Objects.requireNonNull(func);
+            return (k, v) -> {
+                V newValue = func.apply(k, v);
+                typeCheck(k, newValue);
+                return newValue;
+            };
+        }
+
         private String badKeyMsg(Object key) {
             return "Attempt to insert " + key.getClass() +
-                " key into map with key type " + keyType;
+                    " key into map with key type " + keyType;
         }
 
         private String badValueMsg(Object value) {
             return "Attempt to insert " + value.getClass() +
-                " value into map with value type " + valueType;
+                    " value into map with value type " + valueType;
         }
 
         CheckedMap(Map<K, V> m, Class<K> keyType, Class<V> valueType) {
@@ -2768,7 +2895,7 @@
                 Object v = e.getValue();
                 typeCheck(k, v);
                 checked.add(
-                    new AbstractMap.SimpleImmutableEntry<>((K) k, (V) v));
+                        new AbstractMap.SimpleImmutableEntry<>((K)k, (V)v));
             }
             for (Map.Entry<K,V> e : checked)
                 m.put(e.getKey(), e.getValue());
@@ -2782,6 +2909,74 @@
             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 +3651,67 @@
 
         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) {
+            Objects.requireNonNull(function);
+        }
+
+        @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 +3875,65 @@
             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
--- a/src/share/classes/java/util/HashMap.java	Tue Apr 16 13:26:30 2013 +0100
+++ b/src/share/classes/java/util/HashMap.java	Tue Apr 16 11:17:19 2013 -0700
@@ -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 <tt>Map</tt> interface.  This
@@ -376,6 +380,13 @@
         return null == entry ? null : entry.getValue();
     }
 
+    @Override
+    public V getOrDefault(Object key, V defaultValue) {
+        Entry<K,V> entry = getEntry(key);
+
+        return (entry == null) ? defaultValue : entry.getValue();
+    }
+
     /**
      * Returns <tt>true</tt> if this map contains a mapping for the
      * specified key.
@@ -603,6 +614,261 @@
         return (e == null ? null : e.value);
     }
 
+    // optimized implementations of default methods in Map
+
+    @Override
+    public V putIfAbsent(K key, V value) {
+        if (table == EMPTY_TABLE) {
+            inflateTable(threshold);
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)table[i];
+        for(; e != null; e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                if(e.value != null) {
+                    return e.value;
+                }
+                e.value = value;
+                modCount++;
+                e.recordAccess(this);
+                return null;
+            }
+        }
+
+        modCount++;
+        addEntry(hash, key, value, i);
+        return null;
+    }
+
+    @Override
+    public boolean remove(Object key, Object value) {
+        if (isEmpty()) {
+            return false;
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> prev = (Entry<K,V>)table[i];
+        Entry<K,V> e = prev;
+
+        while (e != null) {
+            Entry<K,V> next = e.next;
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                if (!Objects.equals(e.value, value)) {
+                    return false;
+                }
+                modCount++;
+                size--;
+                if (prev == e)
+                    table[i] = next;
+                else
+                    prev.next = next;
+                e.recordRemoval(this);
+                return true;
+            }
+            prev = e;
+            e = next;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean replace(K key, V oldValue, V newValue) {
+        if (isEmpty()) {
+            return false;
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)table[i];
+        for (; e != null; e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key) && Objects.equals(e.value, oldValue)) {
+                e.value = newValue;
+                e.recordAccess(this);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public V replace(K key, V value) {
+        if (isEmpty()) {
+            return null;
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)table[i];
+        for (; e != null; e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V oldValue = e.value;
+                e.value = value;
+                e.recordAccess(this);
+                return oldValue;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        if (table == EMPTY_TABLE) {
+            inflateTable(threshold);
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)table[i];
+        for (; e != null; e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V oldValue = e.value;
+                return oldValue == null ? (e.value = mappingFunction.apply(key)) : oldValue;
+            }
+        }
+
+        V newValue = mappingFunction.apply(key);
+        if (newValue != null) {
+            modCount++;
+            addEntry(hash, key, newValue, i);
+        }
+
+        return newValue;
+    }
+
+    @Override
+    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        if (isEmpty()) {
+            return null;
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> prev = (Entry<K,V>)table[i];
+        Entry<K,V> e = prev;
+
+        while (e != null) {
+            Entry<K,V> next = e.next;
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V oldValue = e.value;
+                if (oldValue == null)
+                    break;
+                V newValue = remappingFunction.apply(key, oldValue);
+                modCount++;
+                if (newValue == null) {
+                    size--;
+                    if (prev == e)
+                        table[i] = next;
+                    else
+                        prev.next = next;
+                    e.recordRemoval(this);
+                } else {
+                    e.value = newValue;
+                    e.recordAccess(this);
+                }
+                return newValue;
+            }
+            prev = e;
+            e = next;
+        }
+
+        return null;
+    }
+
+    @Override
+    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        if (table == EMPTY_TABLE) {
+            inflateTable(threshold);
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> prev = (Entry<K,V>)table[i];
+        Entry<K,V> e = prev;
+
+        while (e != null) {
+            Entry<K,V> next = e.next;
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V oldValue = e.value;
+                V newValue = remappingFunction.apply(key, oldValue);
+                if (newValue != oldValue) {
+                    modCount++;
+                    if (newValue == null) {
+                        size--;
+                        if (prev == e)
+                            table[i] = next;
+                        else
+                            prev.next = next;
+                        e.recordRemoval(this);
+                    } else {
+                        e.value = newValue;
+                        e.recordAccess(this);
+                    }
+                }
+                return newValue;
+            }
+            prev = e;
+            e = next;
+        }
+
+        V newValue = remappingFunction.apply(key, null);
+        if (newValue != null) {
+            modCount++;
+            addEntry(hash, key, newValue, i);
+        }
+
+        return newValue;
+    }
+
+    @Override
+    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        if (table == EMPTY_TABLE) {
+            inflateTable(threshold);
+        }
+        int hash = (key == null) ? 0 : hash(key);
+        int i = indexFor(hash, table.length);
+        @SuppressWarnings("unchecked")
+        Entry<K,V> prev = (Entry<K,V>)table[i];
+        Entry<K,V> e = prev;
+
+        while (e != null) {
+            Entry<K,V> next = e.next;
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V oldValue = e.value;
+                V newValue = remappingFunction.apply(oldValue, value);
+                modCount++;
+                if (newValue == null) {
+                    size--;
+                    if (prev == e)
+                        table[i] = next;
+                    else
+                        prev.next = next;
+                    e.recordRemoval(this);
+                } else {
+                    e.value = newValue;
+                    e.recordAccess(this);
+                }
+                return newValue;
+            }
+            prev = e;
+            e = next;
+        }
+
+        if (value != null) {
+            modCount++;
+            addEntry(hash, key, value, i);
+        }
+
+        return value;
+    }
+
+    // end of optimized implementations of default methods in Map
+
     /**
      * Removes and returns the entry associated with the specified key
      * in the HashMap.  Returns null if the HashMap contains no mapping
@@ -697,8 +963,8 @@
             return containsNullValue();
 
         Entry<?,?>[] tab = table;
-        for (int i = 0; i < tab.length ; i++)
-            for (Entry<?,?> e = tab[i] ; e != null ; e = e.next)
+        for (int i = 0; i < tab.length; i++)
+            for (Entry<?,?> e = tab[i]; e != null; e = e.next)
                 if (value.equals(e.value))
                     return true;
         return false;
@@ -709,8 +975,8 @@
      */
     private boolean containsNullValue() {
         Entry<?,?>[] tab = table;
-        for (int i = 0; i < tab.length ; i++)
-            for (Entry<?,?> e = tab[i] ; e != null ; e = e.next)
+        for (int i = 0; i < tab.length; i++)
+            for (Entry<?,?> e = tab[i]; e != null; e = e.next)
                 if (e.value == null)
                     return true;
         return false;
--- a/src/share/classes/java/util/Hashtable.java	Tue Apr 16 13:26:30 2013 +0100
+++ b/src/share/classes/java/util/Hashtable.java	Tue Apr 16 11:17:19 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 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.BiConsumer;
+import java.util.function.Function;
+import java.util.function.BiFunction;
 
 /**
  * This class implements a hash table, which maps keys to values. Any
@@ -455,6 +459,26 @@
         }
     }
 
+    private void addEntry(int hash, K key, V value, int index) {
+        modCount++;
+
+        Entry<?,?> tab[] = table;
+        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<K,V> e = (Entry<K,V>) tab[index];
+        tab[index] = new Entry<>(hash, key, value, e);
+        count++;
+    }
+
     /**
      * Maps the specified <code>key</code> to the specified
      * <code>value</code> in this hashtable. Neither the key nor the
@@ -492,21 +516,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<K,V> e = (Entry<K,V>)tab[index];
-        tab[index] = new Entry<>(hash, key, value, e);
-        count++;
+        addEntry(hash, key, value, index);
         return null;
     }
 
@@ -892,6 +902,239 @@
         return h;
     }
 
+    @Override
+    public synchronized V getOrDefault(Object key, V defaultValue) {
+        V result = get(key);
+        return (null == result) ? defaultValue : result;
+    }
+
+    @Override
+    public synchronized void forEach(BiConsumer<? super K, ? super V> action) {
+        Objects.requireNonNull(action);     // explicit check required in case
+                                            // table is empty.
+        Entry<?,?>[] tab = table;
+        for (Entry<?,?> entry : tab) {
+            while (entry != null) {
+                action.accept((K)entry.key, (V)entry.value);
+                entry = entry.next;
+            }
+        }
+    }
+
+    @Override
+    public synchronized void replaceAll(
+            BiFunction<? super K, ? super V, ? extends V> function) {
+        Map.super.replaceAll(function);
+    }
+
+    @Override
+    public synchronized V putIfAbsent(K key, V value) {
+        Objects.requireNonNull(value);
+
+        // Makes sure the key is not already in the hashtable.
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> entry = (Entry<K,V>)tab[index];
+        for (; entry != null; entry = entry.next) {
+            if ((entry.hash == hash) && entry.key.equals(key)) {
+                V old = entry.value;
+                if (old == null) {
+                    entry.value = value;
+                }
+                return old;
+            }
+        }
+
+        addEntry(hash, key, value, index);
+        return null;
+    }
+
+    @Override
+    public synchronized boolean remove(Object key, Object value) {
+        Objects.requireNonNull(value);
+
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key) && e.value.equals(value)) {
+                modCount++;
+                if (prev != null) {
+                    prev.next = e.next;
+                } else {
+                    tab[index] = e.next;
+                }
+                count--;
+                e.value = null;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public synchronized boolean replace(K key, V oldValue, V newValue) {
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key)) {
+                if (e.value.equals(oldValue)) {
+                    e.value = newValue;
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public synchronized V replace(K key, V value) {
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if ((e.hash == hash) && e.key.equals(key)) {
+                V oldValue = e.value;
+                e.value = value;
+                return oldValue;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public synchronized V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
+        Objects.requireNonNull(mappingFunction);
+
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (; e != null; e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                // Hashtable not accept null value
+                return e.value;
+            }
+        }
+
+        V newValue = mappingFunction.apply(key);
+        if (newValue != null) {
+            addEntry(hash, key, newValue, index);
+        }
+
+        return newValue;
+    }
+
+    @Override
+    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                V newValue = remappingFunction.apply(key, e.value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && Objects.equals(e.key, key)) {
+                V newValue = remappingFunction.apply(key, e.value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+
+        V newValue = remappingFunction.apply(key, null);
+        if (newValue != null) {
+            addEntry(hash, key, newValue, index);
+        }
+
+        return newValue;
+    }
+
+    @Override
+    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        Objects.requireNonNull(remappingFunction);
+
+        Entry<?,?> tab[] = table;
+        int hash = hash(key);
+        int index = (hash & 0x7FFFFFFF) % tab.length;
+        @SuppressWarnings("unchecked")
+        Entry<K,V> e = (Entry<K,V>)tab[index];
+        for (Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
+            if (e.hash == hash && e.key.equals(key)) {
+                V newValue = remappingFunction.apply(e.value, value);
+                if (newValue == null) {
+                    modCount++;
+                    if (prev != null) {
+                        prev.next = e.next;
+                    } else {
+                        tab[index] = e.next;
+                    }
+                    count--;
+                } else {
+                    e.value = newValue;
+                }
+                return newValue;
+            }
+        }
+
+        if (value != null) {
+            addEntry(hash, key, value, index);
+        }
+
+        return value;
+    }
+
     /**
      * Save the state of the Hashtable to a stream (i.e., serialize it).
      *
--- a/src/share/classes/java/util/Map.java	Tue Apr 16 13:26:30 2013 +0100
+++ b/src/share/classes/java/util/Map.java	Tue Apr 16 11:17:19 2013 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2011, 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
@@ -25,6 +25,10 @@
 
 package java.util;
 
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
 /**
  * An object that maps keys to values.  A map cannot contain duplicate keys;
  * each key can map to at most one value.
@@ -475,4 +479,613 @@
      */
     int hashCode();
 
+    // Defaultable methods
+
+    /**
+    *  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
+    * 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
+    * (<a href="Collection.html#optional-restrictions">optional</a>)
+    * @throws NullPointerException if the specified key is null and this map
+    * does not permit null keys
+    * (<a href="Collection.html#optional-restrictions">optional</a>)
+    */
+    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 (which may be unspecified), until
+     * all entries have been processed or the action throws an {@code Exception}.
+     * 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}:
+     * <pre> {@code
+     * for ((Map.Entry<K, V> entry : map.entrySet())
+     *     action.accept(entry.getKey(), entry.getValue());
+     * }</pre>
+     *
+     * @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<K, V> entry : entrySet()) {
+            K k;
+            V v;
+            try {
+                k = entry.getKey();
+                v = entry.getValue();
+            } catch(IllegalStateException ise) {
+                throw new ConcurrentModificationException(ise);
+            }
+            action.accept(k, v);
+        }
+    }
+
+    /**
+     * Replaces each entry's value with the result of invoking the given
+     * function on that entry, in the order entries are returned by an entry
+     * set iterator, until all entries have been processed or the function
+     * throws an exception.
+     *
+     * <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
+     * for ((Map.Entry<K, V> entry : map.entrySet())
+     *     entry.setValue(function.apply(entry.getKey(), entry.getValue()));
+     * }</pre>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if function or a replacement value is null,
+     *         and this map does not permit null keys or values
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws IllegalArgumentException if some property of a replacement value
+     *         prevents it from being stored in this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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<K, V> entry : entrySet()) {
+            K k;
+            V v;
+            try {
+                k = entry.getKey();
+                v = entry.getValue();
+            } catch(IllegalStateException ise) {
+                throw new ConcurrentModificationException(ise);
+            }
+            entry.setValue(function.apply(k, v));
+        }
+    }
+
+    /**
+     * If the specified key is not already associated with a value (or is mapped
+     * 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);
+     * }</pre>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the key or value is of an inappropriate
+     *         type for this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified key or value is null,
+     *         and this map does not permit null keys or values
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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();
+            }
+        }
+
+        return v;
+    }
+
+    /**
+     * 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}:
+     *
+     * <pre> {@code
+     * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+     *     map.remove(key);
+     *     return true;
+     * } else
+     *     return false;
+     * }</pre>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the key or value is of an inappropriate
+     *         type for this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws NullPointerException if the specified key or value is null,
+     *         and this map does not permit null keys or values
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @since 1.8
+     */
+    default boolean remove(Object key, Object value) {
+        Object curValue = get(key);
+        if (!Objects.equals(curValue, value) ||
+            (curValue == null && !containsKey(key))) {
+            return false;
+        }
+        remove(key);
+        return true;
+    }
+
+    /**
+     * 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}:
+     *
+     * <pre> {@code
+     * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
+     *     map.put(key, newValue);
+     *     return true;
+     * } else
+     *     return false;
+     * }</pre>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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) {
+        Object curValue = get(key);
+        if (!Objects.equals(curValue, oldValue) ||
+            (curValue == null && !containsKey(key))) {
+            return false;
+        }
+        put(key, newValue);
+        return true;
+    }
+
+    /**
+     * 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}:
+     *
+     * <pre> {@code
+     * if (map.containsKey(key)) {
+     *     return map.put(key, value);
+     * } else
+     *     return null;
+     * }</pre>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the class of the specified key or value
+     *         prevents it from being stored in this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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}.
+     *
+     * <p>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:
+     *
+     * <pre> {@code
+     * map.computeIfAbsent(key, k -> new Value(f(k)));
+     * }</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>
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the class of the specified key or value
+     *         prevents it from being stored in this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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.
+     *
+     * <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.
+     *
+     * <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 {@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.
+     *
+     * @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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the class of the specified key or value
+     *         prevents it from being stored in this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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:
+     *
+     * <pre> {@code
+     * map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))}</pre>
+     * (Method {@link #merge merge()} is often simpler to use for such purposes.)
+     *
+     * <p>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.
+     *
+     * <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
+     * {@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>
+     *
+     * 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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the class of the specified key or value
+     *         prevents it from being stored in this map
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @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 value or is
+     * associated with null, associates it with the given value.
+     * Otherwise, replaces the value with the results of the given
+     * remapping function, or removes if the result is {@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:
+     *
+     * <pre> {@code
+     * map.merge(key, msg, String::concat)
+     * }</pre>
+     *
+     * <p>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.
+     *
+     * <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 {@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, oldValue);
+     * else if (oldValue == null)
+     *     map.putIfAbsent(key, newValue);
+     * else
+     *     map.replace(key, oldValue, newValue);
+     * }</pre>
+     *
+     * 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
+     *         (<a href="Collection.html#optional-restrictions">optional</a>)
+     * @throws ClassCastException if the class of the specified key or value
+     *         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
+     * @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;
+                }
+            }
+        }
+    }
 }
--- a/src/share/classes/java/util/concurrent/ConcurrentMap.java	Tue Apr 16 13:26:30 2013 +0100
+++ b/src/share/classes/java/util/concurrent/ConcurrentMap.java	Tue Apr 16 11:17:19 2013 -0700
@@ -38,7 +38,7 @@
 
 /**
  * A {@link java.util.Map} providing additional atomic
- * <tt>putIfAbsent</tt>, <tt>remove</tt>, and <tt>replace</tt> methods.
+ * {@code putIfAbsent}, {@code remove}, and {@code replace} methods.
  *
  * <p>Memory consistency effects: As with other concurrent
  * collections, actions in a thread prior to placing an object into a
@@ -57,6 +57,21 @@
  * @param <V> the type of mapped values
  */
 public interface ConcurrentMap<K, V> extends Map<K, V> {
+
+    /**
+     * {@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.
+     */
+    @Override
+    default V getOrDefault(Object key, V defaultValue) {
+        V v;
+        return ((v = get(key)) != null) ? v : defaultValue;
+    }
+
     /**
      * If the specified key is not already associated
      * with a value, associate it with the given value.
@@ -91,7 +106,7 @@
      * Removes the entry for a key only if currently mapped to a given value.
      * This is equivalent to
      *  <pre> {@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 <tt>true</tt> if the value was removed
-     * @throws UnsupportedOperationException if the <tt>remove</tt> 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
      *  <pre> {@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 <tt>true</tt> if the value was replaced
-     * @throws UnsupportedOperationException if the <tt>put</tt> 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
-     *         <tt>null</tt> if there was no mapping for the key.
-     *         (A <tt>null</tt> return can also indicate that the map
-     *         previously associated <tt>null</tt> 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 <tt>put</tt> 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/Map/Defaults.java	Tue Apr 16 11:17:19 2013 -0700
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8010122 8004518
+ * @summary Test Map default methods
+ * @author Mike Duigou
+ * @run testng Defaults
+ */
+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;
+
+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<IntegerEnum,String>")
+    public void testGetOrDefaultNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertSame(map.get(null), map.getOrDefault(null, EXTRA_VALUE), "values should match");
+    }
+
+    @Test(dataProvider = "Map<IntegerEnum,String>")
+    public void testGetOrDefault(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]), "expected key missing");
+        assertSame(map.get(KEYS[1]), map.getOrDefault(KEYS[1], EXTRA_VALUE), "values should match");
+        assertFalse(map.containsKey(EXTRA_KEY), "expected absent key");
+        assertSame(map.getOrDefault(EXTRA_KEY, EXTRA_VALUE), EXTRA_VALUE, "value not returned as default");
+        assertNull(map.getOrDefault(EXTRA_KEY, null), "null not returned as default");
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testPutIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
+        assertTrue(map.containsKey(null), "null key absent");
+        assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
+        assertSame(map.putIfAbsent(null, null), EXTRA_VALUE, "previous not expected value");
+        assertTrue(map.containsKey(null), "null key absent");
+        assertSame(map.get(null), EXTRA_VALUE, "unexpected value");
+        assertSame(map.remove(null), EXTRA_VALUE, "removed unexpected value");
+
+        assertFalse(map.containsKey(null), description + ": key present after remove");
+        assertNull(map.putIfAbsent(null, null), "previous not null");
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertNull(map.putIfAbsent(null, EXTRA_VALUE), "previous not null");
+        assertSame(map.get(null), EXTRA_VALUE, "value not expected");
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testPutIfAbsent(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]));
+        Object expected = map.get(KEYS[1]);
+        assertTrue(null == expected || expected == VALUES[1]);
+        assertSame(map.putIfAbsent(KEYS[1], EXTRA_VALUE), expected);
+        assertSame(map.get(KEYS[1]), expected);
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertSame(map.putIfAbsent(EXTRA_KEY, EXTRA_VALUE), null);
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+    }
+
+    @Test(dataProvider = "Nulls Map<IntegerEnum,String>")
+    public void testForEach(String description, Map<IntegerEnum, String> map) {
+        IntegerEnum[] EACH_KEY = new IntegerEnum[map.size()];
+
+        map.forEach((k, v) -> {
+            int idx = (null == k) ? 0 : k.ordinal(); // substitute for index.
+            assertNull(EACH_KEY[idx]);
+            EACH_KEY[idx] = (idx == 0) ? KEYS[0] : k; // substitute for comparison.
+            assertSame(v, map.get(k));
+        });
+
+        assertEquals(KEYS, EACH_KEY);
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public static void testRemoveNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertFalse(map.remove(null, EXTRA_VALUE), description);
+        assertTrue(map.containsKey(null));
+        assertNull(map.get(null));
+        assertTrue(map.remove(null, null));
+        assertFalse(map.containsKey(null));
+        assertNull(map.get(null));
+        assertFalse(map.remove(null, null));
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public static void testRemove(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]));
+        Object expected = map.get(KEYS[1]);
+        assertTrue(null == expected || expected == VALUES[1]);
+        assertFalse(map.remove(KEYS[1], EXTRA_VALUE), description);
+        assertSame(map.get(KEYS[1]), expected);
+        assertTrue(map.remove(KEYS[1], expected));
+        assertNull(map.get(KEYS[1]));
+        assertFalse(map.remove(KEYS[1], expected));
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertFalse(map.remove(EXTRA_KEY, EXTRA_VALUE));
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testReplaceKVNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertSame(map.replace(null, EXTRA_VALUE), null);
+        assertSame(map.get(null), EXTRA_VALUE);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testReplaceKV(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]));
+        Object expected = map.get(KEYS[1]);
+        assertTrue(null == expected || expected == VALUES[1]);
+        assertSame(map.replace(KEYS[1], EXTRA_VALUE), expected);
+        assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertNull(map.replace(EXTRA_KEY, EXTRA_VALUE));
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertNull(map.get(EXTRA_KEY));
+        assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+        assertSame(map.replace(EXTRA_KEY, (String)expected), EXTRA_VALUE);
+        assertSame(map.get(EXTRA_KEY), expected);
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testReplaceKVVNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertFalse(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
+        assertNull(map.get(null));
+        assertTrue(map.replace(null, null, EXTRA_VALUE));
+        assertSame(map.get(null), EXTRA_VALUE);
+        assertTrue(map.replace(null, EXTRA_VALUE, EXTRA_VALUE));
+        assertSame(map.get(null), EXTRA_VALUE);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testReplaceKVV(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]));
+        Object expected = map.get(KEYS[1]);
+        assertTrue(null == expected || expected == VALUES[1]);
+        assertFalse(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
+        assertSame(map.get(KEYS[1]), expected);
+        assertTrue(map.replace(KEYS[1], (String)expected, EXTRA_VALUE));
+        assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+        assertTrue(map.replace(KEYS[1], EXTRA_VALUE, EXTRA_VALUE));
+        assertSame(map.get(KEYS[1]), EXTRA_VALUE);
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertFalse(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertNull(map.get(EXTRA_KEY));
+        assertNull(map.put(EXTRA_KEY, EXTRA_VALUE));
+        assertTrue(map.containsKey(EXTRA_KEY));
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+        assertTrue(map.replace(EXTRA_KEY, EXTRA_VALUE, EXTRA_VALUE));
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testComputeIfAbsentNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertSame(map.computeIfAbsent(null, (k) -> EXTRA_VALUE), EXTRA_VALUE, description);
+        assertSame(map.get(null), EXTRA_VALUE, description);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testComputeIfAbsent(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(KEYS[1]));
+        Object expected = map.get(KEYS[1]);
+        assertTrue(null == expected || expected == VALUES[1], description + String.valueOf(expected));
+        expected = (null == expected) ? EXTRA_VALUE : expected;
+        assertSame(map.computeIfAbsent(KEYS[1], (k) -> EXTRA_VALUE), expected, description);
+        assertSame(map.get(KEYS[1]), expected, description);
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertSame(map.computeIfAbsent(EXTRA_KEY, (k) -> EXTRA_VALUE), EXTRA_VALUE);
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testComputeIfPresentNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null));
+        assertNull(map.get(null));
+        assertSame(map.computeIfPresent(null, (k, v) -> {
+            fail();
+            return EXTRA_VALUE;
+        }), null, description);
+        assertTrue(map.containsKey(null));
+        assertSame(map.get(null), null, description);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testComputeIfPresent(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));
+        Object expected = (null == value) ? null : EXTRA_VALUE;
+        assertSame(map.computeIfPresent(KEYS[1], (k, v) -> {
+            assertSame(v, value);
+            return EXTRA_VALUE;
+        }), expected, description);
+        assertSame(map.get(KEYS[1]), expected, description);
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertSame(map.computeIfPresent(EXTRA_KEY, (k, v) -> {
+            fail();
+            return EXTRA_VALUE;
+        }), null);
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertSame(map.get(EXTRA_KEY), null);
+    }
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    public void testComputeNulls(String description, Map<IntegerEnum, String> map) {
+        assertTrue(map.containsKey(null), "null key absent");
+        assertNull(map.get(null), "value not null");
+        assertSame(map.compute(null, (k, v) -> {
+            assertSame(k, null);
+            assertNull(v);
+            return EXTRA_VALUE;
+        }), EXTRA_VALUE, description);
+        assertTrue(map.containsKey(null));
+        assertSame(map.get(null), EXTRA_VALUE, description);
+        assertSame(map.remove(null), EXTRA_VALUE, "removed value not expected");
+        assertFalse(map.containsKey(null), "null key present");
+        assertSame(map.compute(null, (k, v) -> {
+            assertSame(k, null);
+            assertNull(v);
+            return null;
+        }), null, description);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    public void testCompute(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.compute(KEYS[1], (k, v) -> {
+            assertSame(k, KEYS[1]);
+            assertSame(v, value);
+            return EXTRA_VALUE;
+        }), EXTRA_VALUE, description);
+        assertSame(map.get(KEYS[1]), EXTRA_VALUE, description);
+        assertNull(map.compute(KEYS[1], (k, v) -> {
+            assertSame(v, EXTRA_VALUE);
+            return null;
+        }), description);
+        assertFalse(map.containsKey(KEYS[1]));
+
+        assertFalse(map.containsKey(EXTRA_KEY));
+        assertSame(map.compute(EXTRA_KEY, (k, v) -> {
+            assertNull(v);
+            return EXTRA_VALUE;
+        }), EXTRA_VALUE);
+        assertTrue(map.containsKey(EXTRA_KEY));
+        assertSame(map.get(EXTRA_KEY), EXTRA_VALUE);
+    }
+
+
+    @Test(dataProvider = "R/W Nulls Map<IntegerEnum,String>")
+    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);
+    }
+
+    @Test(dataProvider = "R/W Map<IntegerEnum,String>")
+    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);
+    }
+
+    enum IntegerEnum {
+
+        e0, e1, e2, e3, e4, e5, e6, e7, e8, e9,
+        e10, e11, e12, e13, e14, e15, e16, e17, e18, e19,
+        e20, e21, e22, e23, e24, e25, e26, e27, e28, e29,
+        e30, e31, e32, e33, e34, e35, e36, e37, e38, e39,
+        e40, e41, e42, e43, e44, e45, e46, e47, e48, e49,
+        e50, e51, e52, e53, e54, e55, e56, e57, e58, e59,
+        e60, e61, e62, e63, e64, e65, e66, e67, e68, e69,
+        e70, e71, e72, e73, e74, e75, e76, e77, e78, e79,
+        e80, e81, e82, e83, e84, e85, e86, e87, e88, e89,
+        e90, e91, e92, e93, e94, e95, e96, e97, e98, e99,
+        EXTRA_KEY;
+        public static final int SIZE = values().length;
+    };
+    private static final int TEST_SIZE = IntegerEnum.SIZE - 1;
+    /**
+     * Realized keys ensure that there is always a hard ref to all test objects.
+     */
+    private static final IntegerEnum[] KEYS = new IntegerEnum[TEST_SIZE];
+    /**
+     * Realized values ensure that there is always a hard ref to all test
+     * objects.
+     */
+    private static final String[] VALUES = new String[TEST_SIZE];
+
+    static {
+        IntegerEnum[] keys = IntegerEnum.values();
+        for (int each = 0; each < TEST_SIZE; each++) {
+            KEYS[each] = keys[each];
+            VALUES[each] = String.valueOf(each);
+        }
+    }
+    private static final IntegerEnum EXTRA_KEY = IntegerEnum.EXTRA_KEY;
+    private static final String EXTRA_VALUE = String.valueOf(TEST_SIZE);
+
+    @DataProvider(name = "Map<IntegerEnum,String>", parallel = true)
+    public static Iterator<Object[]> allNullsMapProvider() {
+        return makeAllMaps().iterator();
+    }
+
+    @DataProvider(name = "Nulls Map<IntegerEnum,String>", parallel = true)
+    public static Iterator<Object[]> allMapProvider() {
+        return makeRWMaps(true).iterator();
+    }
+
+    @DataProvider(name = "R/W Map<IntegerEnum,String>", parallel = true)
+    public static Iterator<Object[]> rwMapProvider() {
+        return makeRWMapsNoNulls().iterator();
+    }
+
+    @DataProvider(name = "R/W Nulls Map<IntegerEnum,String>", parallel = true)
+    public static Iterator<Object[]> rwNullsMapProvider() {
+        return makeRWMaps(true).iterator();
+    }
+
+    private static Collection<Object[]> makeAllMapsNoNulls() {
+        Collection<Object[]> all = new ArrayList<>();
+
+        all.addAll(makeRWMaps(false));
+        all.addAll(makeRWNoNullsMaps());
+        all.addAll(makeROMaps(false));
+
+        return all;
+    }
+
+    private static Collection<Object[]> makeRWMapsNoNulls() {
+        Collection<Object[]> all = new ArrayList<>();
+
+        all.addAll(makeRWMaps(false));
+        all.addAll(makeRWNoNullsMaps());
+
+        return all;
+    }
+
+    private static Collection<Object[]> makeAllMaps() {
+        Collection<Object[]> all = new ArrayList<>();
+
+        all.addAll(makeROMaps(false));
+        all.addAll(makeRWMaps(false));
+        all.addAll(makeRWNoNullsMaps());
+        all.addAll(makeRWMaps(true));
+        all.addAll(makeROMaps(true));
+
+        return all;
+    }
+
+    private static Collection<Object[]> makeAllRWMaps() {
+        Collection<Object[]> all = new ArrayList<>();
+
+        all.addAll(makeRWMaps(false));
+        all.addAll(makeRWNoNullsMaps());
+        all.addAll(makeRWMaps(true));
+
+        return all;
+    }
+
+    private static Collection<Object[]> makeRWMaps(boolean nulls) {
+        return Arrays.asList(
+            new Object[]{"HashMap", makeMap(HashMap::new, nulls)},
+            new Object[]{"IdentityHashMap", makeMap(IdentityHashMap::new, nulls)},
+            new Object[]{"LinkedHashMap", makeMap(LinkedHashMap::new, nulls)},
+            new Object[]{"WeakHashMap", makeMap(WeakHashMap::new, nulls)},
+            new Object[]{"Collections.checkedMap(HashMap)", Collections.checkedMap(makeMap(HashMap::new, nulls), IntegerEnum.class, String.class)},
+            new Object[]{"Collections.synchronizedMap(HashMap)", Collections.synchronizedMap(makeMap(HashMap::new, nulls))},
+            new Object[]{"ExtendsAbstractMap", makeMap(ExtendsAbstractMap::new, nulls)});
+    }
+
+    private static Collection<Object[]> makeRWNoNullsMaps() {
+        return Arrays.asList(
+            // null hostile
+            new Object[]{"EnumMap", makeMap(() -> new EnumMap(IntegerEnum.class), false)},
+            new Object[]{"Hashtable", makeMap(Hashtable::new, false)},
+            new Object[]{"TreeMap", makeMap(TreeMap::new, false)},
+            new Object[]{"ConcurrentHashMap", makeMap(ConcurrentHashMap::new, false)},
+            new Object[]{"ConcurrentSkipListMap", makeMap(ConcurrentSkipListMap::new, false)},
+            new Object[]{"Collections.checkedMap(ConcurrentHashMap)", Collections.checkedMap(makeMap(ConcurrentHashMap::new, false), IntegerEnum.class, String.class)},
+            new Object[]{"Collections.synchronizedMap(EnumMap)", Collections.synchronizedMap(makeMap(() -> new EnumMap(IntegerEnum.class), false))},
+            new Object[]{"ImplementsConcurrentMap", makeMap(ImplementsConcurrentMap::new, false)});
+    }
+
+    private static Collection<Object[]> makeROMaps(boolean nulls) {
+        return Arrays.asList(new Object[][]{
+            new Object[]{"Collections.unmodifiableMap(HashMap)", Collections.unmodifiableMap(makeMap(HashMap::new, nulls))}
+        });
+    }
+
+    private static Map<IntegerEnum, String> makeMap(Supplier<Map<IntegerEnum, String>> supplier, boolean nulls) {
+        Map<IntegerEnum, String> result = supplier.get();
+
+        for (int each = 0; each < TEST_SIZE; each++) {
+            if (nulls) {
+                result.put((each == 0) ? null : KEYS[each], null);
+            } else {
+                result.put(KEYS[each], VALUES[each]);
+            }
+        }
+
+        return result;
+    }
+
+    public interface Thrower<T extends Throwable> {
+
+        public void run() throws T;
+    }
+
+    public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable) {
+        assertThrows(thrower, throwable, null);
+    }
+
+    public static <T extends Throwable> void assertThrows(Thrower<T> thrower, Class<T> throwable, String message) {
+        Throwable result;
+        try {
+            thrower.run();
+            result = null;
+        } catch (Throwable caught) {
+            result = caught;
+        }
+
+        assertInstance(result, throwable,
+            (null != message)
+            ? message
+            : "Failed to throw " + throwable.getCanonicalName());
+    }
+
+    public static <T> void assertInstance(T actual, Class<? extends T> expected) {
+        assertInstance(expected.isInstance(actual), null);
+    }
+
+    public static <T> void assertInstance(T actual, Class<? extends T> expected, String message) {
+        assertTrue(expected.isInstance(actual), message);
+    }
+
+    /**
+     * A simple mutable map implementation that provides only default
+     * implementations of all methods. ie. none of the Map interface default
+     * methods have overridden implementations.
+     *
+     * @param <K> Type of keys
+     * @param <V> Type of values
+     */
+    public static class ExtendsAbstractMap<M extends Map<K,V>, K, V> extends AbstractMap<K, V> {
+
+        protected final M map;
+
+        public ExtendsAbstractMap() { this( (M) new HashMap<K,V>()); }
+
+        protected ExtendsAbstractMap(M map) { this.map = map; }
+
+        public Set<Map.Entry<K, V>> entrySet() {
+            return new AbstractSet<Map.Entry<K, V>>() {
+                public int size() {
+                    return map.size();
+                }
+
+                public Iterator<Map.Entry<K,V>> iterator() {
+                    final Iterator<Map.Entry<K,V>> source = map.entrySet().iterator();
+                    return new Iterator<Map.Entry<K,V>>() {
+                       public boolean hasNext() { return source.hasNext(); }
+                       public Map.Entry<K,V> next() { return source.next(); }
+                       public void remove() { source.remove(); }
+                    };
+                }
+
+                public boolean add(Map.Entry<K,V> e) {
+                    return map.entrySet().add(e);
+                }
+            };
+        }
+
+        public V put(K key, V value) {
+            return map.put(key, value);
+        }
+    }
+
+    /**
+     * A simple mutable concurrent map implementation that provides only default
+     * implementations of all methods. ie. none of the ConcurrentMap interface
+     * default methods have overridden implementations.
+     *
+     * @param <K> Type of keys
+     * @param <V> Type of values
+     */
+    public static class ImplementsConcurrentMap<K, V> extends ExtendsAbstractMap<ConcurrentMap<K,V>, K, V> implements ConcurrentMap<K,V> {
+        public ImplementsConcurrentMap() { super(new ConcurrentHashMap<K,V>()); }
+
+        // ConcurrentMap reabstracts these methods
+
+        public V replace(K k, V v) { return map.replace(k, v); };
+
+        public boolean replace(K k, V v, V vv) { return map.replace(k, v, vv); };
+
+        public boolean remove(Object k, Object v) { return map.remove(k, v); }
+
+        public V putIfAbsent(K k, V v) { return map.putIfAbsent(k, v); }
+    }
+}