# HG changeset patch
# User mduigou
# Date 1366061246 25200
# Node ID c34df0a87068a8ae447dff503be6c1ff0f3ab4a1
# Parent 5c406a7471927223390f3845ca5f93cb22450489
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
, Henry Jen , Akhil Arora , Peter Levart
diff --git a/src/share/classes/java/util/Collections.java b/src/share/classes/java/util/Collections.java
--- a/src/share/classes/java/util/Collections.java
+++ b/src/share/classes/java/util/Collections.java
@@ -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
@@ -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)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 @@
*
* Failure to follow this advice may result in non-deterministic behavior.
*
- *
The returned collection does not pass the hashCode
- * and equals operations through to the backing collection, but
- * relies on Object's equals and hashCode methods. This is
+ *
The returned collection does not pass the {@code hashCode}
+ * and {@code equals} operations through to the backing collection, but
+ * relies on {@code Object}'s equals and hashCode methods. This is
* necessary to preserve the contracts of these operations in the case
* that the backing collection is a set or a list.
*
@@ -2107,6 +2171,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 iterator() {
+ // JDK-6363904 - unwrapped iterator could be typecast to
+ // ListIterator with unsafe set()
final Iterator it = c.iterator();
return new Iterator() {
public boolean hasNext() { return it.hasNext(); }
@@ -2717,6 +2834,17 @@
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;
@@ -2782,6 +2910,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 +3652,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 +3876,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
diff --git a/src/share/classes/java/util/HashMap.java b/src/share/classes/java/util/HashMap.java
--- a/src/share/classes/java/util/HashMap.java
+++ b/src/share/classes/java/util/HashMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,11 @@
*/
package java.util;
+
import java.io.*;
+import java.util.function.Consumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
/**
* Hash table based implementation of the Map interface. This
@@ -376,6 +380,13 @@
return null == entry ? null : entry.getValue();
}
+ @Override
+ public V getOrDefault(Object key, V defaultValue) {
+ Entry entry = getEntry(key);
+
+ return (entry == null) ? defaultValue : entry.getValue();
+ }
+
/**
* Returns true 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 e = (Entry)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 prev = (Entry)table[i];
+ Entry e = prev;
+
+ while (e != null) {
+ Entry 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 e = (Entry)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 e = (Entry)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 e = (Entry)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 prev = (Entry)table[i];
+ Entry e = prev;
+
+ while (e != null) {
+ Entry 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 prev = (Entry)table[i];
+ Entry e = prev;
+
+ while (e != null) {
+ Entry 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 prev = (Entry)table[i];
+ Entry e = prev;
+
+ while (e != null) {
+ Entry 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;
diff --git a/src/share/classes/java/util/Hashtable.java b/src/share/classes/java/util/Hashtable.java
--- a/src/share/classes/java/util/Hashtable.java
+++ b/src/share/classes/java/util/Hashtable.java
@@ -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 e = (Entry) tab[index];
+ tab[index] = new Entry<>(hash, key, value, e);
+ count++;
+ }
+
/**
* Maps the specified key to the specified
* value 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 e = (Entry)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 entry = (Entry)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 e = (Entry)tab[index];
+ for (Entry 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 e = (Entry)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 e = (Entry)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 e = (Entry)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 e = (Entry)tab[index];
+ for (Entry 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 e = (Entry)tab[index];
+ for (Entry 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 e = (Entry)tab[index];
+ for (Entry 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).
*
diff --git a/src/share/classes/java/util/Map.java b/src/share/classes/java/util/Map.java
--- a/src/share/classes/java/util/Map.java
+++ b/src/share/classes/java/util/Map.java
@@ -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.
+ *
+ *
The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value to which the specified key is mapped, or
+ * {@code defaultValue} if this map contains no mapping for the key
+ * @throws ClassCastException if the key is of an inappropriate type for
+ * this map
+ * (optional)
+ * @throws NullPointerException if the specified key is null and this map
+ * does not permit null keys
+ * (optional)
+ */
+ default V getOrDefault(Object key, V defaultValue) {
+ V v;
+ return (((v = get(key)) != null) || containsKey(key))
+ ? v
+ : defaultValue;
+ }
+
+ /**
+ * Performs the given action on each entry in this map, in the order entries
+ * are returned by an entry set iterator (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.
+ *
+ *
The default implementation should be overridden by implementations if
+ * they can provide a more performant implementation than an iterator-based
+ * one.
+ *
+ *
The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties.
+ *
+ * @implSpec The default implementation is equivalent to, for this
+ * {@code map}:
+ *
+ *
+ * @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 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.
+ *
+ *
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}:
+ *
+ *
+ * @param function the function to apply to each entry
+ * @throws UnsupportedOperationException if the {@code set} operation
+ * is not supported by this map's entry set iterator.
+ * @throws ClassCastException if the class of a replacement value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if the specified function is null, or the
+ * specified replacement value is null, and this map does not permit null
+ * values
+ * @throws ClassCastException if a replacement value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if function or a replacement value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @throws IllegalArgumentException if some property of a replacement value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws ConcurrentModificationException if an entry is found to be
+ * removed during iteration
+ * @since 1.8
+ */
+ default void replaceAll(BiFunction super K, ? super V, ? extends V> function) {
+ Objects.requireNonNull(function);
+ for (Map.Entry 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.
+ *
+ *
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}:
+ *
+ *
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key, or
+ * {@code 1} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * (optional)
+ * @throws ConcurrentModificationException if a modification of the map is
+ * detected during insertion of the value.
+ * @since 1.8
+ */
+ default V putIfAbsent(K key, V value) {
+ V v = get(key);
+ if (v == null) {
+ if (put(key, value) != null) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+ return v;
+ }
+
+ /**
+ * Removes the entry for the specified key only if it is currently
+ * mapped to the specified value.
+ *
+ *
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}:
+ *
+ *
+ *
+ * @param key key with which the specified value is associated
+ * @param value value expected to be associated with the specified key
+ * @return {@code true} if the value was removed
+ * @throws UnsupportedOperationException if the {@code remove} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the key or value is of an inappropriate
+ * type for this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * (optional)
+ * @since 1.8
+ */
+ default boolean remove(Object key, Object value) {
+ 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.
+ *
+ *
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}:
+ *
+ *
+ *
+ * @param key key with which the specified value is associated
+ * @param oldValue value expected to be associated with the specified key
+ * @param newValue value to be associated with the specified key
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of a specified key or value
+ * prevents it from being stored in this map
+ * @throws NullPointerException if a specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of a specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default boolean replace(K key, V oldValue, V newValue) {
+ 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.
+ *
+ *
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}:
+ *
+ *
+ *
+ * @param key key with which the specified value is associated
+ * @param value value to be associated with the specified key
+ * @return the previous value associated with the specified key, or
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
+ * if the implementation supports null values.)
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws NullPointerException if the specified key or value is null,
+ * and this map does not permit null keys or values
+ * @throws IllegalArgumentException if some property of the specified key
+ * or value prevents it from being stored in this map
+ * @since 1.8
+ */
+ default V replace(K key, V value) {
+ return containsKey(key) ? put(key, value) : null;
+ }
+
+ /**
+ * If the specified key is not already associated with a value (or
+ * is mapped to {@code null}), attempts to compute its value using
+ * the given mapping function and enters it into this map unless
+ * {@code null}.
+ *
+ *
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:
+ *
+ *
{@code
+ * map.computeIfAbsent(key, k -> new Value(f(k)));
+ * }
+ *
+ *
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:
+ *
+ *
{@code
+ * if (map.get(key) == null) {
+ * V newValue = mappingFunction.apply(key);
+ * if (newValue != null)
+ * map.putIfAbsent(key, newValue);
+ * }
+ * }
+ *
+ * @param key key with which the specified value is to be associated
+ * @param mappingFunction the function to compute a value
+ * @return the current (existing or computed) value associated with
+ * the specified key, or null if the computed value is null
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * mappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V computeIfAbsent(K key,
+ Function super K, ? extends V> mappingFunction) {
+ V v, newValue;
+ return ((v = get(key)) == null &&
+ (newValue = mappingFunction.apply(key)) != null &&
+ (v = putIfAbsent(key, newValue)) == null) ? newValue : v;
+ }
+
+ /**
+ * If the value for the specified key is present and non-null, attempts to
+ * compute a new mapping given the key and its current mapped value.
+ *
+ *
If the function returns {@code null}, the mapping is removed. If the
+ * function itself throws an (unchecked) exception, the exception is
+ * rethrown, and the current mapping is left unchanged.
+ *
+ *
The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the
+ * following steps for this {@code map}, then returning the
+ * current value or {@code null} if now absent:
+ *
+ *
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V computeIfPresent(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ V oldValue;
+ while ((oldValue = get(key)) != null) {
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue))
+ return null;
+ }
+ return oldValue;
+ }
+
+ /**
+ * Attempts to compute a mapping for the specified key and its
+ * current mapped value (or {@code null} if there is no current
+ * mapping). For example, to either create or append a {@code
+ * String msg} to a value mapping:
+ *
+ *
+ * (Method {@link #merge merge()} is often simpler to use for such purposes.)
+ *
+ *
If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ *
The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the following
+ * steps for this {@code map}, then returning the current value or
+ * {@code null} if absent:
+ *
+ *
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param remappingFunction the function to compute a value
+ * @return the new value associated with the specified key, or null if none
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @since 1.8
+ */
+ default V compute(K key,
+ BiFunction super K, ? super V, ? extends V> remappingFunction) {
+ V oldValue = get(key);
+ for (;;) {
+ V newValue = remappingFunction.apply(key, oldValue);
+ if (oldValue != null) {
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (newValue != null) {
+ if ((oldValue = putIfAbsent(key, newValue)) == null)
+ return newValue;
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+
+ /**
+ * If the specified key is not already associated with a 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:
+ *
+ *
If the function returns {@code null}, the mapping is removed (or
+ * remains absent if initially absent). If the function itself throws an
+ * (unchecked) exception, the exception is rethrown, and the current mapping
+ * is left unchanged.
+ *
+ *
The default implementation makes no guarantees about synchronization
+ * or atomicity properties of this method. Any implementation providing
+ * atomicity guarantees must override this method and document its
+ * concurrency properties. In particular, all implementations of
+ * subinterface {@link java.util.concurrent.ConcurrentMap} must document
+ * whether the function is applied once atomically only if the value is not
+ * present. Any class that permits null values must document
+ * whether and how this method distinguishes absence from null mappings.
+ *
+ * @implSpec
+ * The default implementation is equivalent to performing the
+ * following steps for this {@code map}, then returning the
+ * current value or {@code null} if absent:
+ *
+ *
+ *
+ * In concurrent contexts, the default implementation may retry
+ * these steps when multiple threads attempt updates.
+ *
+ * @param key key with which the specified value is to be associated
+ * @param value the value to use if absent
+ * @param remappingFunction the function to recompute a value if present
+ * @return the new value associated with the specified key, or null if none
+ * @throws UnsupportedOperationException if the {@code put} operation
+ * is not supported by this map
+ * (optional)
+ * @throws ClassCastException if the class of the specified key or value
+ * prevents it from being stored in this map
+ * (optional)
+ * @throws NullPointerException if the specified key is null and
+ * this map does not support null keys, or the
+ * remappingFunction is null
+ * @since 1.8
+ */
+ default V merge(K key, V value,
+ BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ V oldValue = get(key);
+ for (;;) {
+ if (oldValue != null) {
+ V newValue = remappingFunction.apply(oldValue, value);
+ if (newValue != null) {
+ if (replace(key, oldValue, newValue))
+ return newValue;
+ } else if (remove(key, oldValue)) {
+ return null;
+ }
+ oldValue = get(key);
+ } else {
+ if (value == null) {
+ return null;
+ }
+
+ if ((oldValue = putIfAbsent(key, value)) == null) {
+ return value;
+ }
+ }
+ }
+ }
}
diff --git a/src/share/classes/java/util/concurrent/ConcurrentMap.java b/src/share/classes/java/util/concurrent/ConcurrentMap.java
--- a/src/share/classes/java/util/concurrent/ConcurrentMap.java
+++ b/src/share/classes/java/util/concurrent/ConcurrentMap.java
@@ -38,7 +38,7 @@
/**
* A {@link java.util.Map} providing additional atomic
- * putIfAbsent, remove, and replace methods.
+ * {@code putIfAbsent}, {@code remove}, and {@code replace} methods.
*
*
Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
@@ -57,6 +57,21 @@
* @param the type of mapped values
*/
public interface ConcurrentMap extends Map {
+
+ /**
+ * {@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
*
{@code
- * if (map.containsKey(key) && map.get(key).equals(value)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.remove(key);
* return true;
* } else
@@ -101,8 +116,8 @@
*
* @param key key with which the specified value is associated
* @param value value expected to be associated with the specified key
- * @return true if the value was removed
- * @throws UnsupportedOperationException if the remove operation
+ * @return {@code true} if the value was removed
+ * @throws UnsupportedOperationException if the {@code remove} operation
* is not supported by this map
* @throws ClassCastException if the key or value is of an inappropriate
* type for this map
@@ -117,7 +132,7 @@
* Replaces the entry for a key only if currently mapped to a given value.
* This is equivalent to
*
{@code
- * if (map.containsKey(key) && map.get(key).equals(oldValue)) {
+ * if (map.containsKey(key) && Objects.equals(map.get(key), oldValue)) {
* map.put(key, newValue);
* return true;
* } else
@@ -128,8 +143,8 @@
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
- * @return true if the value was replaced
- * @throws UnsupportedOperationException if the put operation
+ * @return {@code true} if the value was replaced
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of a specified key or value
* prevents it from being stored in this map
@@ -154,11 +169,11 @@
* @param key key with which the specified value is associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
- * null if there was no mapping for the key.
- * (A null return can also indicate that the map
- * previously associated null with the key,
+ * {@code null} if there was no mapping for the key.
+ * (A {@code null} return can also indicate that the map
+ * previously associated {@code null} with the key,
* if the implementation supports null values.)
- * @throws UnsupportedOperationException if the put operation
+ * @throws UnsupportedOperationException if the {@code put} operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
diff --git a/test/java/util/Map/ToArray.java b/test/java/util/Map/Defaults.java
copy from test/java/util/Map/ToArray.java
copy to test/java/util/Map/Defaults.java
--- a/test/java/util/Map/ToArray.java
+++ b/test/java/util/Map/Defaults.java
@@ -23,165 +23,572 @@
/*
* @test
- * @bug 8008785
- * @summary Ensure toArray() implementations return correct results.
+ * @bug 8010122 8004518
+ * @summary Test Map default methods
* @author Mike Duigou
+ * @run testng Defaults
*/
-import java.util.*;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.function.Supplier;
-public class ToArray {
+import org.testng.annotations.Test;
+import org.testng.annotations.DataProvider;
+import static org.testng.Assert.fail;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+
+public class Defaults {
+
+ @Test(dataProvider = "Nulls Map")
+ public void testGetOrDefaultNulls(String description, Map 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")
+ public void testGetOrDefault(String description, Map 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")
+ public void testPutIfAbsentNulls(String description, Map 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")
+ public void testPutIfAbsent(String description, Map 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")
+ public void testForEach(String description, Map 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")
+ public static void testRemoveNulls(String description, Map 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")
+ public static void testRemove(String description, Map 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")
+ public void testReplaceKVNulls(String description, Map 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")
+ public void testReplaceKV(String description, Map 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")
+ public void testReplaceKVVNulls(String description, Map 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")
+ public void testReplaceKVV(String description, Map 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")
+ public void testComputeIfAbsentNulls(String description, Map 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")
+ public void testComputeIfAbsent(String description, Map 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")
+ public void testComputeIfPresentNulls(String description, Map 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")
+ public void testComputeIfPresent(String description, Map 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")
+ public void testComputeNulls(String description, Map 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")
+ public void testCompute(String description, Map 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")
+ public void testMergeNulls(String description, Map 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")
+ public void testMerge(String description, Map 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", parallel = true)
+ public static Iterator