Index: modules/luni/src/main/java/java/util/IdentityHashMap.java =================================================================== --- modules/luni/src/main/java/java/util/IdentityHashMap.java (revision 658500) +++ modules/luni/src/main/java/java/util/IdentityHashMap.java (working copy) @@ -23,16 +23,25 @@ import java.io.Serializable; /** - * HashMap is the hash table based implementation of the Map interface. + * IdentityHashMap * - * This implementation provides all of the optional map operations, and permits - * null values and the null key. (The HashMap class is roughly equivalent to - * Hashtable, except that it is unsynchronized and permits nulls.) + * This is a variant on HashMap which tests equality by reference instead of by + * value. Basically, keys and values are compared for equality by checking if + * their references are equal rather than by calling the "equals" function. + * + * IdentityHashMap uses open addressing (linear probing in particular) for + * collision resolution. This is different from HashMap which uses Chaining. + * + * Like HashMap, IdentityHashMap is not thread safe, so access by multiple + * threads must be synchronized by an external mechanism such as + * Collections.synchronizedMap. + * + * @since 1.4 */ -public class HashMap extends AbstractMap implements Map, +public class IdentityHashMap extends AbstractMap implements Map, Cloneable, Serializable { - private static final long serialVersionUID = 362498820763181265L; + private static final long serialVersionUID = 8188218128353913216L; /* * Actual count of entries @@ -46,12 +55,12 @@ /* * modification count, to keep track of structural modifications between the - * HashMap and the iterator + * IdentityHashMap and the iterator */ transient int modCount = 0; /* - * default size that an HashMap created using the default constructor would + * default size that an IdentityHashMap created using the default constructor would * have. */ private static final int DEFAULT_SIZE = 16; @@ -60,7 +69,7 @@ * maximum ratio of (stored elements)/(storage size) which does not lead to * rehash */ - final float loadFactor; + final float loadFactor = 0.75f; /* * maximum number of elements that can be put in this map before having to @@ -101,9 +110,9 @@ Entry currentEntry; Entry prevEntry; - final HashMap associatedMap; + final IdentityHashMap associatedMap; - AbstractMapIterator(HashMap hm) { + AbstractMapIterator(IdentityHashMap hm) { associatedMap = hm; expectedModCount = hm.modCount; futureEntry = null; @@ -169,7 +178,7 @@ private static class EntryIterator extends AbstractMapIterator implements Iterator> { - EntryIterator (HashMap map) { + EntryIterator (IdentityHashMap map) { super(map); } @@ -181,7 +190,7 @@ private static class KeyIterator extends AbstractMapIterator implements Iterator { - KeyIterator (HashMap map) { + KeyIterator (IdentityHashMap map) { super(map); } @@ -193,7 +202,7 @@ private static class ValueIterator extends AbstractMapIterator implements Iterator { - ValueIterator (HashMap map) { + ValueIterator (IdentityHashMap map) { super(map); } @@ -203,14 +212,14 @@ } } - static class HashMapEntrySet extends AbstractSet> { - private final HashMap associatedMap; + static class IdentityHashMapEntrySet extends AbstractSet> { + private final IdentityHashMap associatedMap; - public HashMapEntrySet(HashMap hm) { + public IdentityHashMapEntrySet(IdentityHashMap hm) { associatedMap = hm; } - HashMap hashMap() { + IdentityHashMap associatedMap() { return associatedMap; } @@ -272,26 +281,33 @@ } /** - * Constructs a new empty instance of HashMap. + * Constructs a new empty instance of IdentityHashMap. * */ - public HashMap() { + public IdentityHashMap() { this(DEFAULT_SIZE); } /** - * Constructs a new instance of HashMap with the specified capacity. + * Constructs a new instance of IdentityHashMap with the specified capacity. * * @param capacity - * the initial capacity of this HashMap + * the initial capacity of this IdentityHashMap * * @exception IllegalArgumentException * when the capacity is less than zero */ - public HashMap(int capacity) { - this(capacity, 0.75f); // default load factor of 0.75 + public IdentityHashMap(int capacity) { + if (capacity >= 0) { + capacity = calculateCapacity(capacity); + elementCount = 0; + elementData = newElementArray(capacity); + computeThreshold(); + } else { + throw new IllegalArgumentException(); } - + } + /** * Calculates the capacity of storage required for storing given number of * elements @@ -317,45 +333,21 @@ } /** - * Constructs a new instance of HashMap with the specified capacity and load - * factor. - * - * - * @param capacity - * the initial capacity - * @param loadFactor - * the initial load factor - * - * @exception IllegalArgumentException - * when the capacity is less than zero or the load factor is - * less or equal to zero - */ - public HashMap(int capacity, float loadFactor) { - if (capacity >= 0 && loadFactor > 0) { - capacity = calculateCapacity(capacity); - elementCount = 0; - elementData = newElementArray(capacity); - this.loadFactor = loadFactor; - computeThreshold(); - } else { - throw new IllegalArgumentException(); - } - } - - /** - * Constructs a new instance of HashMap containing the mappings from the + * Constructs a new instance of IdentityHashMap containing the mappings from the * specified Map. * * @param map * the mappings to add */ - public HashMap(Map map) { + public IdentityHashMap(Map map) { this(calculateCapacity(map.size())); - putAllImpl(map); + if(map.entrySet() != null) { + putAllImpl(map); + } } /** - * Removes all mappings from this HashMap, leaving it empty. + * Removes all mappings from this IdentityHashMap, leaving it empty. * * @see #isEmpty * @see #size @@ -370,9 +362,9 @@ } /** - * Answers a new HashMap with the same mappings and size as this HashMap. + * Answers a new IdentityHashMap with the same mappings and size as this IdentityHashMap. * - * @return a shallow copy of this HashMap + * @return a shallow copy of this IdentityHashMap * * @see java.lang.Cloneable */ @@ -380,7 +372,7 @@ @SuppressWarnings("unchecked") public Object clone() { try { - HashMap map = (HashMap) super.clone(); + IdentityHashMap map = (IdentityHashMap) super.clone(); map.elementCount = 0; map.elementData = newElementArray(elementData.length); map.putAll(this); @@ -399,11 +391,11 @@ } /** - * Searches this HashMap for the specified key. + * Searches this IdentityHashMap for the specified key. * * @param key * the object to search for - * @return true if key is a key of this HashMap, false + * @return true if key is a key of this IdentityHashMap, false * otherwise */ @Override @@ -413,11 +405,11 @@ } /** - * Searches this HashMap for the specified value. + * Searches this IdentityHashMap for the specified value. * * @param value * the object to search for - * @return true if value is a value of this HashMap, false + * @return true if value is a value of this IdentityHashMap, false * otherwise */ @Override @@ -447,15 +439,15 @@ } /** - * Answers a Set of the mappings contained in this HashMap. Each element in - * the set is a Map.Entry. The set is backed by this HashMap so changes to + * Answers a Set of the mappings contained in this IdentityHashMap. Each element in + * the set is a Map.Entry. The set is backed by this IdentityHashMap so changes to * one are reflected by the other. The set does not support adding. * * @return a Set of the mappings */ @Override public Set> entrySet() { - return new HashMapEntrySet(this); + return new IdentityHashMapEntrySet(this); } /** @@ -502,9 +494,48 @@ } /** - * Answers if this HashMap has no elements, a size of zero. + * Compares this map with other objects. This map is equal to another map is + * it represents the same set of mappings. With this map, two mappings are + * the same if both the key and the value are equal by reference. + * + * When compared with a map that is not an IdentityHashMap, the equals + * method is not necessarily symmetric (a.equals(b) implies b.equals(a)) nor + * transitive (a.equals(b) and b.equals(c) implies a.equals(c)). + * + * @return whether the argument object is equal to this object + */ + @Override + public boolean equals(Object object) { + /* + * We need to override the equals method in AbstractMap because + * AbstractMap.equals will call ((Map) object).entrySet().contains() to + * determine equality of the entries, so it will defer to the argument + * for comparison, meaning that reference-based comparison will not take + * place. We must ensure that all comparison is implemented by methods + * in this class (or in one of our inner classes) for reference-based + * comparison to take place. + */ + if (this == object) { + return true; + } + if (object instanceof Map) { + Map map = (Map) object; + if (size() != map.size()) { + return false; + } + + Set> set = entrySet(); + // ensure we use the equals method of the set created by "this" + return set.equals(map.entrySet()); + } + return false; + } + + + /** + * Answers if this IdentityHashMap has no elements, a size of zero. * - * @return true if this HashMap has no elements, false otherwise + * @return true if this IdentityHashMap has no elements, false otherwise * * @see #size */ @@ -514,8 +545,8 @@ } /** - * Answers a Set of the keys contained in this HashMap. The set is backed by - * this HashMap so changes to one are reflected by the other. The set does + * Answers a Set of the keys contained in this IdentityHashMap. The set is backed by + * this IdentityHashMap so changes to one are reflected by the other. The set does * not support adding. * * @return a Set of the keys @@ -531,23 +562,23 @@ @Override public int size() { - return HashMap.this.size(); + return IdentityHashMap.this.size(); } @Override public void clear() { - HashMap.this.clear(); + IdentityHashMap.this.clear(); } @Override public boolean remove(Object key) { - Entry entry = HashMap.this.removeEntry(key); + Entry entry = IdentityHashMap.this.removeEntry(key); return entry != null; } @Override public Iterator iterator() { - return new KeyIterator (HashMap.this); + return new KeyIterator (IdentityHashMap.this); } }; } @@ -663,12 +694,12 @@ } /** - * Removes a mapping with the specified key from this HashMap. + * Removes a mapping with the specified key from this IdentityHashMap. * * @param key * the key of the mapping to remove * @return the value of the removed mapping or null if key is not a key in - * this HashMap + * this IdentityHashMap */ @Override public V remove(Object key) { @@ -728,9 +759,9 @@ } /** - * Answers the number of mappings in this HashMap. + * Answers the number of mappings in this IdentityHashMap. * - * @return the number of mappings in this HashMap + * @return the number of mappings in this IdentityHashMap */ @Override public int size() { @@ -738,8 +769,8 @@ } /** - * Answers a Collection of the values contained in this HashMap. The - * collection is backed by this HashMap so changes to one are reflected by + * Answers a Collection of the values contained in this IdentityHashMap. The + * collection is backed by this IdentityHashMap so changes to one are reflected by * the other. The collection does not support adding. * * @return a Collection of the values @@ -755,17 +786,29 @@ @Override public int size() { - return HashMap.this.size(); + return IdentityHashMap.this.size(); } @Override public void clear() { - HashMap.this.clear(); + IdentityHashMap.this.clear(); } @Override + public boolean remove(Object object) { + Iterator it = iterator(); + while (it.hasNext()) { + if (object == it.next()) { + it.remove(); + return true; + } + } + return false; + } + + @Override public Iterator iterator() { - return new ValueIterator (HashMap.this); + return new ValueIterator (IdentityHashMap.this); } }; } @@ -789,9 +832,9 @@ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); - int length = stream.readInt(); - elementData = newElementArray(length); elementCount = stream.readInt(); + int length = calculateCapacity(elementCount); + elementData = newElementArray(length); for (int i = elementCount; --i >= 0;) { K key = (K) stream.readObject(); int index = (null == key) ? 0 : (computeHashCode(key) & (length - 1)); @@ -803,15 +846,15 @@ * Contract-related functionality */ static int computeHashCode(Object key) { - return key.hashCode(); + return System.identityHashCode(key); } static boolean areEqualKeys(Object key1, Object key2) { - return key1.equals(key2); + return (key1 == key2); } static boolean areEqualValues(Object value1, Object value2) { - return value1.equals(value2); + return (value1 == value2); }