8199318: add idempotent copy operation for Map.Entry
Reviewed-by: alanb, psandoz, dfuchs
This commit is contained in:
parent
b27599b3ec
commit
cd0678fcf6
src/java.base/share/classes/java/util
test/jdk/java/util/Map
@ -592,8 +592,11 @@ public abstract class AbstractMap<K,V> implements Map<K,V> {
|
||||
|
||||
/**
|
||||
* An Entry maintaining a key and a value. The value may be
|
||||
* changed using the {@code setValue} method. This class
|
||||
* facilitates the process of building custom map
|
||||
* changed using the {@code setValue} method. Instances of
|
||||
* this class are not associated with any map's entry-set view.
|
||||
*
|
||||
* @apiNote
|
||||
* This class facilitates the process of building custom map
|
||||
* implementations. For example, it may be convenient to return
|
||||
* arrays of {@code SimpleEntry} instances in method
|
||||
* {@code Map.entrySet().toArray}.
|
||||
@ -725,10 +728,21 @@ public abstract class AbstractMap<K,V> implements Map<K,V> {
|
||||
}
|
||||
|
||||
/**
|
||||
* An Entry maintaining an immutable key and value. This class
|
||||
* does not support method {@code setValue}. This class may be
|
||||
* convenient in methods that return thread-safe snapshots of
|
||||
* key-value mappings.
|
||||
* An unmodifiable Entry maintaining a key and a value. This class
|
||||
* does not support the {@code setValue} method. Instances of
|
||||
* this class are not associated with any map's entry-set view.
|
||||
*
|
||||
* @apiNote
|
||||
* Instances of this class are not necessarily immutable, as the key
|
||||
* and value may be mutable. An instance of <i>this specific class</i>
|
||||
* is unmodifiable, because the key and value references cannot be
|
||||
* changed. A reference of this <i>type</i> may not be unmodifiable,
|
||||
* as a subclass may be modifiable or may provide the appearance of modifiability.
|
||||
* <p>
|
||||
* This class may be convenient in methods that return thread-safe snapshots of
|
||||
* key-value mappings. For alternatives, see the
|
||||
* {@link Map#entry Map::entry} and {@link Map.Entry#copyOf Map.Entry::copyOf}
|
||||
* methods.
|
||||
*
|
||||
* @since 1.6
|
||||
*/
|
||||
@ -788,7 +802,10 @@ public abstract class AbstractMap<K,V> implements Map<K,V> {
|
||||
* Replaces the value corresponding to this entry with the specified
|
||||
* value (optional operation). This implementation simply throws
|
||||
* {@code UnsupportedOperationException}, as this class implements
|
||||
* an <i>immutable</i> map entry.
|
||||
* an unmodifiable map entry.
|
||||
*
|
||||
* @implSpec
|
||||
* The implementation in this class always throws {@code UnsupportedOperationException}.
|
||||
*
|
||||
* @param value new value to be stored in this entry
|
||||
* @return (Does not return)
|
||||
|
@ -393,14 +393,33 @@ public interface Map<K, V> {
|
||||
Set<Map.Entry<K, V>> entrySet();
|
||||
|
||||
/**
|
||||
* A map entry (key-value pair). The {@code Map.entrySet} method returns
|
||||
* a collection-view of the map, whose elements are of this class. The
|
||||
* <i>only</i> way to obtain a reference to a map entry is from the
|
||||
* iterator of this collection-view. These {@code Map.Entry} objects are
|
||||
* valid <i>only</i> for the duration of the iteration; more formally,
|
||||
* the behavior of a map entry is undefined if the backing map has been
|
||||
* modified after the entry was returned by the iterator, except through
|
||||
* the {@code setValue} operation on the map entry.
|
||||
* A map entry (key-value pair). The Entry may be unmodifiable, or the
|
||||
* value may be modifiable if the optional {@code setValue} method is
|
||||
* implemented. The Entry may be independent of any map, or it may represent
|
||||
* an entry of the entry-set view of a map.
|
||||
* <p>
|
||||
* Instances of the {@code Map.Entry} interface may be obtained by iterating
|
||||
* the entry-set view of a map. These instances maintain a connection to the
|
||||
* original, backing map. This connection to the backing map is valid
|
||||
* <i>only</i> for the duration of iteration over the entry-set view.
|
||||
* During iteration of the entry-set view, if supported by the backing map,
|
||||
* a change to a {@code Map.Entry}'s value via the
|
||||
* {@link Map.Entry#setValue setValue} method will be visible in the backing map.
|
||||
* The behavior of such a {@code Map.Entry} instance is undefined outside of
|
||||
* iteration of the map's entry-set view. It is also undefined if the backing
|
||||
* map has been modified after the {@code Map.Entry} was returned by the
|
||||
* iterator, except through the {@code Map.Entry.setValue} method. In particular,
|
||||
* a change to the value of a mapping in the backing map might or might not be
|
||||
* visible in the corresponding {@code Map.Entry} element of the entry-set view.
|
||||
*
|
||||
* @apiNote
|
||||
* It is possible to create a {@code Map.Entry} instance that is disconnected
|
||||
* from a backing map by using the {@link Map.Entry#copyOf copyOf} method. For example,
|
||||
* the following creates a snapshot of a map's entries that is guaranteed not to
|
||||
* change even if the original map is modified:
|
||||
* <pre> {@code
|
||||
* var entries = map.entrySet().stream().map(Map.Entry::copyOf).toList()
|
||||
* }</pre>
|
||||
*
|
||||
* @see Map#entrySet()
|
||||
* @since 1.2
|
||||
@ -559,6 +578,37 @@ public interface Map<K, V> {
|
||||
return (Comparator<Map.Entry<K, V>> & Serializable)
|
||||
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the given {@code Map.Entry}. The returned instance is not
|
||||
* associated with any map. The returned instance has the same characteristics
|
||||
* as instances returned by the {@link Map#entry Map::entry} method.
|
||||
*
|
||||
* @apiNote
|
||||
* An instance obtained from a map's entry-set view has a connection to that map.
|
||||
* The {@code copyOf} method may be used to create a {@code Map.Entry} instance,
|
||||
* containing the same key and value, that is independent of any map.
|
||||
*
|
||||
* @implNote
|
||||
* If the given entry was obtained from a call to {@code copyOf} or {@code Map::entry},
|
||||
* calling {@code copyOf} will generally not create another copy.
|
||||
*
|
||||
* @param <K> the type of the key
|
||||
* @param <V> the type of the value
|
||||
* @param e the entry to be copied
|
||||
* @return a map entry equal to the given entry
|
||||
* @throws NullPointerException if e is null or if either of its key or value is null
|
||||
* @since 17
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> Map.Entry<K, V> copyOf(Map.Entry<? extends K, ? extends V> e) {
|
||||
Objects.requireNonNull(e);
|
||||
if (e instanceof KeyValueHolder) {
|
||||
return (Map.Entry<K, V>) e;
|
||||
} else {
|
||||
return Map.entry(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comparison and hashing
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2021, 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
|
||||
@ -34,7 +34,6 @@ import java.util.Iterator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
@ -45,8 +44,8 @@ import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertNotEquals;
|
||||
import static org.testng.Assert.assertNotSame;
|
||||
import static org.testng.Assert.assertSame;
|
||||
import static org.testng.Assert.assertThrows;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
/*
|
||||
* @test
|
||||
@ -470,7 +469,7 @@ public class MapFactories {
|
||||
Map<Integer, String> copy = Map.copyOf(map);
|
||||
}
|
||||
|
||||
// Map.entry() tests
|
||||
// Map::entry tests
|
||||
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
public void entryWithNullKeyDisallowed() {
|
||||
@ -482,6 +481,12 @@ public class MapFactories {
|
||||
Map.Entry<Integer,String> e = Map.entry(0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySetValueDisallowed() {
|
||||
var e = Map.entry("a", "b");
|
||||
assertThrows(UnsupportedOperationException.class, () -> e.setValue("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryBasicTests() {
|
||||
Map.Entry<String,String> kvh1 = Map.entry("xyzzy", "plugh");
|
||||
@ -492,8 +497,54 @@ public class MapFactories {
|
||||
assertTrue(sie.equals(kvh1));
|
||||
assertFalse(kvh2.equals(sie));
|
||||
assertFalse(sie.equals(kvh2));
|
||||
assertEquals(sie.hashCode(), kvh1.hashCode());
|
||||
assertEquals(sie.toString(), kvh1.toString());
|
||||
assertEquals(kvh1.hashCode(), sie.hashCode());
|
||||
assertEquals(kvh1.toString(), sie.toString());
|
||||
}
|
||||
|
||||
// Map.Entry::copyOf tests
|
||||
|
||||
@Test(expectedExceptions=NullPointerException.class)
|
||||
public void entryCopyNullDisallowed() {
|
||||
Map.Entry.copyOf(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryCopyWithNullKeyDisallowed() {
|
||||
var e = new AbstractMap.SimpleEntry<>(null, "b");
|
||||
assertThrows(NullPointerException.class, () -> Map.Entry.copyOf(e));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryCopyWithNullValueDisallowed() {
|
||||
var e = new AbstractMap.SimpleEntry<>("a", null);
|
||||
assertThrows(NullPointerException.class, () -> Map.Entry.copyOf(e));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryCopySetValueDisallowed() {
|
||||
var e = new AbstractMap.SimpleEntry<>("a", "b");
|
||||
var c = Map.Entry.copyOf(e);
|
||||
assertThrows(UnsupportedOperationException.class, () -> c.setValue("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entryCopyBasicTests() {
|
||||
Map.Entry<String,String> orig = new AbstractMap.SimpleImmutableEntry<>("xyzzy", "plugh");
|
||||
Map.Entry<String,String> copy1 = Map.Entry.copyOf(orig);
|
||||
Map.Entry<String,String> copy2 = Map.Entry.copyOf(copy1);
|
||||
|
||||
assertEquals(orig, copy1);
|
||||
assertEquals(copy1, orig);
|
||||
assertEquals(orig, copy2);
|
||||
assertEquals(copy2, orig);
|
||||
assertEquals(copy1, copy2);
|
||||
assertEquals(copy2, copy1);
|
||||
|
||||
assertNotSame(orig, copy1);
|
||||
assertSame(copy1, copy2);
|
||||
|
||||
assertEquals(copy1.hashCode(), orig.hashCode());
|
||||
assertEquals(copy1.toString(), orig.toString());
|
||||
}
|
||||
|
||||
// compile-time test of wildcards
|
||||
|
Loading…
x
Reference in New Issue
Block a user