Skip to content

Commit 53905e6

Browse files
stuart-marksStuart Marks
authored andcommitted
8178355: IdentityHashMap uses identity-based comparison for values everywhere except remove(K,V) and replace(K,V,V)
Reviewed-by: smarks
1 parent 25dfcbd commit 53905e6

File tree

2 files changed

+164
-13
lines changed

2 files changed

+164
-13
lines changed

src/java.base/share/classes/java/util/IdentityHashMap.java

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
* designed for use only in the rare cases wherein reference-equality
5050
* semantics are required.</b>
5151
*
52+
* <p>The view collections of this map also have reference-equality semantics
53+
* for their elements. See the {@link keySet() keySet}, {@link values() values},
54+
* and {@link entrySet() entrySet} methods for further information.
55+
*
5256
* <p>A typical use of this class is <i>topology-preserving object graph
5357
* transformations</i>, such as serialization or deep-copying. To perform such
5458
* a transformation, a program must maintain a "node table" that keeps track
@@ -346,7 +350,8 @@ public V get(Object key) {
346350

347351
/**
348352
* Tests whether the specified object reference is a key in this identity
349-
* hash map.
353+
* hash map. Returns {@code true} if and only if this map contains a mapping
354+
* with key {@code k} such that {@code (key == k)}.
350355
*
351356
* @param key possible key
352357
* @return {@code true} if the specified object reference is a key
@@ -370,7 +375,8 @@ public boolean containsKey(Object key) {
370375

371376
/**
372377
* Tests whether the specified object reference is a value in this identity
373-
* hash map.
378+
* hash map. Returns {@code true} if and only if this map contains a mapping
379+
* with value {@code v} such that {@code (value == v)}.
374380
*
375381
* @param value value whose presence in this map is to be tested
376382
* @return {@code true} if this map maps one or more keys to the
@@ -411,8 +417,9 @@ private boolean containsMapping(Object key, Object value) {
411417

412418
/**
413419
* Associates the specified value with the specified key in this identity
414-
* hash map. If the map previously contained a mapping for the key, the
415-
* old value is replaced.
420+
* hash map. If this map already {@link containsKey(Object) contains}
421+
* a mapping for the key, the old value is replaced, otherwise, a new mapping
422+
* is inserted into this map.
416423
*
417424
* @param key the key with which the specified value is to be associated
418425
* @param value the value to be associated with the specified key
@@ -497,8 +504,10 @@ private boolean resize(int newCapacity) {
497504

498505
/**
499506
* Copies all of the mappings from the specified map to this map.
500-
* These mappings will replace any mappings that this map had for
501-
* any of the keys currently in the specified map.
507+
* For each mapping in the specified map, if this map already
508+
* {@link containsKey(Object) contains} a mapping for the key,
509+
* its value is replaced with the value from the specified map;
510+
* otherwise, a new mapping is inserted into this map.
502511
*
503512
* @param m mappings to be stored in this map
504513
* @throws NullPointerException if the specified map is null
@@ -516,6 +525,8 @@ public void putAll(Map<? extends K, ? extends V> m) {
516525

517526
/**
518527
* Removes the mapping for this key from this map if present.
528+
* The mapping is removed if and only if the mapping has a key
529+
* {@code k} such that (key == k).
519530
*
520531
* @param key key whose mapping is to be removed from the map
521532
* @return the previous value associated with {@code key}, or
@@ -632,7 +643,9 @@ public void clear() {
632643
* {@code true} if the given object is also a map and the two maps
633644
* represent identical object-reference mappings. More formally, this
634645
* map is equal to another map {@code m} if and only if
635-
* {@code this.entrySet().equals(m.entrySet())}.
646+
* {@code this.entrySet().equals(m.entrySet())}. See the
647+
* {@link entrySet() entrySet} method for the specification of equality
648+
* of this map's entries.
636649
*
637650
* <p><b>Owing to the reference-equality-based semantics of this map it is
638651
* possible that the symmetry and transitivity requirements of the
@@ -667,8 +680,11 @@ public boolean equals(Object o) {
667680

668681
/**
669682
* Returns the hash code value for this map. The hash code of a map is
670-
* defined to be the sum of the hash codes of each entry in the map's
671-
* {@code entrySet()} view. This ensures that {@code m1.equals(m2)}
683+
* defined to be the sum of the hash codes of each entry of this map.
684+
* See the {@link entrySet() entrySet} method for a specification of the
685+
* hash code of this map's entries.
686+
*
687+
* <p>This specification ensures that {@code m1.equals(m2)}
672688
* implies that {@code m1.hashCode()==m2.hashCode()} for any two
673689
* {@code IdentityHashMap} instances {@code m1} and {@code m2}, as
674690
* required by the general contract of {@link Object#hashCode}.
@@ -1162,7 +1178,9 @@ public Spliterator<V> spliterator() {
11621178
* e.getValue()==o.getValue()}. To accommodate these equals
11631179
* semantics, the {@code hashCode} method returns
11641180
* {@code System.identityHashCode(e.getKey()) ^
1165-
* System.identityHashCode(e.getValue())}.
1181+
* System.identityHashCode(e.getValue())}. (While the keys and values
1182+
* are compared using reference equality, the {@code Map.Entry}
1183+
* objects themselves are not.)
11661184
*
11671185
* <p><b>Owing to the reference-equality-based semantics of the
11681186
* {@code Map.Entry} instances in the set returned by this method,
@@ -1382,6 +1400,50 @@ public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
13821400
}
13831401
}
13841402

1403+
/**
1404+
* {@inheritDoc}
1405+
*
1406+
* <p>More formally, if this map contains a mapping from a key
1407+
* {@code k} to a value {@code v} such that {@code (key == k)}
1408+
* and {@code (value == v)}, then this method removes the mapping
1409+
* for this key and returns {@code true}; otherwise it returns
1410+
* {@code false}.
1411+
*/
1412+
@Override
1413+
public boolean remove(Object key, Object value) {
1414+
return removeMapping(key, value);
1415+
}
1416+
1417+
/**
1418+
* {@inheritDoc}
1419+
*
1420+
* <p>More formally, if this map contains a mapping from a key
1421+
* {@code k} to a value {@code v} such that {@code (key == k)}
1422+
* and {@code (oldValue == v)}, then this method associates
1423+
* {@code k} with {@code newValue} and returns {@code true};
1424+
* otherwise it returns {@code false}.
1425+
*/
1426+
@Override
1427+
public boolean replace(K key, V oldValue, V newValue) {
1428+
Object k = maskNull(key);
1429+
Object[] tab = table;
1430+
int len = tab.length;
1431+
int i = hash(k, len);
1432+
1433+
while (true) {
1434+
Object item = tab[i];
1435+
if (item == k) {
1436+
if (tab[i + 1] != oldValue)
1437+
return false;
1438+
tab[i + 1] = newValue;
1439+
return true;
1440+
}
1441+
if (item == null)
1442+
return false;
1443+
i = nextKeyIndex(i, len);
1444+
}
1445+
}
1446+
13851447
/**
13861448
* Similar form as array-based Spliterators, but skips blank elements,
13871449
* and guestimates size as decreasing by half per split.

test/jdk/java/util/IdentityHashMap/Basic.java

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
/*
4040
* @test
41-
* @bug 8285295
41+
* @bug 8285295 8178355
4242
* @summary Basic tests for IdentityHashMap
4343
* @run testng Basic
4444
*/
@@ -49,8 +49,6 @@
4949
// the identities of Map.Entry instances obtained from the entrySet; however, the keys and
5050
// values they contain are guaranteed to have the right identity.
5151

52-
// TODO remove(k, v)
53-
// TODO replace(k, v1, v2)
5452
// TODO add tests using null keys and values
5553
// TODO deeper testing of view collections including iterators, equals, contains, etc.
5654
// TODO Map.Entry::setValue
@@ -484,6 +482,97 @@ public void testRemoveKey() {
484482
entry(k2, v2));
485483
}
486484

485+
// remove(Object, Object) absent key, absent value
486+
@Test
487+
public void testRemoveAA() {
488+
Box k1c = new Box(k1a);
489+
Box v1c = new Box(v1a);
490+
assertFalse(map.remove(k1c, v1c));
491+
checkEntries(map.entrySet(),
492+
entry(k1a, v1a),
493+
entry(k1b, v1b),
494+
entry(k2, v2));
495+
}
496+
497+
// remove(Object, Object) absent key, present value
498+
@Test
499+
public void testRemoveAV() {
500+
Box k1c = new Box(k1a);
501+
assertFalse(map.remove(k1c, v1a));
502+
checkEntries(map.entrySet(),
503+
entry(k1a, v1a),
504+
entry(k1b, v1b),
505+
entry(k2, v2));
506+
}
507+
508+
// remove(Object, Object) present key, absent value
509+
@Test
510+
public void testRemoveKA() {
511+
Box v1c = new Box(v1a);
512+
assertFalse(map.remove(k1a, v1c));
513+
checkEntries(map.entrySet(),
514+
entry(k1a, v1a),
515+
entry(k1b, v1b),
516+
entry(k2, v2));
517+
}
518+
519+
// remove(Object, Object) present key, present value
520+
@Test
521+
public void testRemoveKV() {
522+
assertTrue(map.remove(k1a, v1a));
523+
checkEntries(map.entrySet(),
524+
entry(k1b, v1b),
525+
entry(k2, v2));
526+
}
527+
528+
// replace(K, V, V) absent key, absent oldValue
529+
@Test
530+
public void testReplaceAA() {
531+
Box k1c = new Box(k1a);
532+
Box v1c = new Box(v1a);
533+
Box newVal = new Box(v2);
534+
assertFalse(map.replace(k1c, v1c, newVal));
535+
checkEntries(map.entrySet(),
536+
entry(k1a, v1a),
537+
entry(k1b, v1b),
538+
entry(k2, v2));
539+
}
540+
541+
// replace(K, V, V) absent key, present oldValue
542+
@Test
543+
public void testReplaceAV() {
544+
Box k1c = new Box(k1a);
545+
Box newVal = new Box(v2);
546+
assertFalse(map.replace(k1c, v1a, newVal));
547+
checkEntries(map.entrySet(),
548+
entry(k1a, v1a),
549+
entry(k1b, v1b),
550+
entry(k2, v2));
551+
}
552+
553+
// replace(K, V, V) present key, absent oldValue
554+
@Test
555+
public void testReplaceKA() {
556+
Box v1c = new Box(v1a);
557+
Box newVal = new Box(v2);
558+
assertFalse(map.replace(k1a, v1c, newVal));
559+
checkEntries(map.entrySet(),
560+
entry(k1a, v1a),
561+
entry(k1b, v1b),
562+
entry(k2, v2));
563+
}
564+
565+
// replace(K, V, V) present key, present oldValue
566+
@Test
567+
public void testReplaceKV() {
568+
Box newVal = new Box(v2);
569+
assertTrue(map.replace(k1a, v1a, newVal));
570+
checkEntries(map.entrySet(),
571+
entry(k1a, newVal),
572+
entry(k1b, v1b),
573+
entry(k2, v2));
574+
}
575+
487576
// AN: key absent, remappingFunction returns null
488577
@Test
489578
public void testComputeAN() {

0 commit comments

Comments
 (0)