Path: blob/master/src/java.base/share/classes/java/util/EnumMap.java
41152 views
/*1* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.util;2627import jdk.internal.access.SharedSecrets;2829/**30* A specialized {@link Map} implementation for use with enum type keys. All31* of the keys in an enum map must come from a single enum type that is32* specified, explicitly or implicitly, when the map is created. Enum maps33* are represented internally as arrays. This representation is extremely34* compact and efficient.35*36* <p>Enum maps are maintained in the <i>natural order</i> of their keys37* (the order in which the enum constants are declared). This is reflected38* in the iterators returned by the collections views ({@link #keySet()},39* {@link #entrySet()}, and {@link #values()}).40*41* <p>Iterators returned by the collection views are <i>weakly consistent</i>:42* they will never throw {@link ConcurrentModificationException} and they may43* or may not show the effects of any modifications to the map that occur while44* the iteration is in progress.45*46* <p>Null keys are not permitted. Attempts to insert a null key will47* throw {@link NullPointerException}. Attempts to test for the48* presence of a null key or to remove one will, however, function properly.49* Null values are permitted.50*51* <P>Like most collection implementations {@code EnumMap} is not52* synchronized. If multiple threads access an enum map concurrently, and at53* least one of the threads modifies the map, it should be synchronized54* externally. This is typically accomplished by synchronizing on some55* object that naturally encapsulates the enum map. If no such object exists,56* the map should be "wrapped" using the {@link Collections#synchronizedMap}57* method. This is best done at creation time, to prevent accidental58* unsynchronized access:59*60* <pre>61* Map<EnumKey, V> m62* = Collections.synchronizedMap(new EnumMap<EnumKey, V>(...));63* </pre>64*65* <p>Implementation note: All basic operations execute in constant time.66* They are likely (though not guaranteed) to be faster than their67* {@link HashMap} counterparts.68*69* <p>This class is a member of the70* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">71* Java Collections Framework</a>.72*73* @author Josh Bloch74* @see EnumSet75* @since 1.576*/77public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>78implements java.io.Serializable, Cloneable79{80/**81* The {@code Class} object for the enum type of all the keys of this map.82*83* @serial84*/85private final Class<K> keyType;8687/**88* All of the values comprising K. (Cached for performance.)89*/90private transient K[] keyUniverse;9192/**93* Array representation of this map. The ith element is the value94* to which universe[i] is currently mapped, or null if it isn't95* mapped to anything, or NULL if it's mapped to null.96*/97private transient Object[] vals;9899/**100* The number of mappings in this map.101*/102private transient int size = 0;103104/**105* Distinguished non-null value for representing null values.106*/107private static final Object NULL = new Object() {108public int hashCode() {109return 0;110}111112public String toString() {113return "java.util.EnumMap.NULL";114}115};116117private Object maskNull(Object value) {118return (value == null ? NULL : value);119}120121@SuppressWarnings("unchecked")122private V unmaskNull(Object value) {123return (V)(value == NULL ? null : value);124}125126/**127* Creates an empty enum map with the specified key type.128*129* @param keyType the class object of the key type for this enum map130* @throws NullPointerException if {@code keyType} is null131*/132public EnumMap(Class<K> keyType) {133this.keyType = keyType;134keyUniverse = getKeyUniverse(keyType);135vals = new Object[keyUniverse.length];136}137138/**139* Creates an enum map with the same key type as the specified enum140* map, initially containing the same mappings (if any).141*142* @param m the enum map from which to initialize this enum map143* @throws NullPointerException if {@code m} is null144*/145public EnumMap(EnumMap<K, ? extends V> m) {146keyType = m.keyType;147keyUniverse = m.keyUniverse;148vals = m.vals.clone();149size = m.size;150}151152/**153* Creates an enum map initialized from the specified map. If the154* specified map is an {@code EnumMap} instance, this constructor behaves155* identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map156* must contain at least one mapping (in order to determine the new157* enum map's key type).158*159* @param m the map from which to initialize this enum map160* @throws IllegalArgumentException if {@code m} is not an161* {@code EnumMap} instance and contains no mappings162* @throws NullPointerException if {@code m} is null163*/164public EnumMap(Map<K, ? extends V> m) {165if (m instanceof EnumMap) {166EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;167keyType = em.keyType;168keyUniverse = em.keyUniverse;169vals = em.vals.clone();170size = em.size;171} else {172if (m.isEmpty())173throw new IllegalArgumentException("Specified map is empty");174keyType = m.keySet().iterator().next().getDeclaringClass();175keyUniverse = getKeyUniverse(keyType);176vals = new Object[keyUniverse.length];177putAll(m);178}179}180181// Query Operations182183/**184* Returns the number of key-value mappings in this map.185*186* @return the number of key-value mappings in this map187*/188public int size() {189return size;190}191192/**193* Returns {@code true} if this map maps one or more keys to the194* specified value.195*196* @param value the value whose presence in this map is to be tested197* @return {@code true} if this map maps one or more keys to this value198*/199public boolean containsValue(Object value) {200value = maskNull(value);201202for (Object val : vals)203if (value.equals(val))204return true;205206return false;207}208209/**210* Returns {@code true} if this map contains a mapping for the specified211* key.212*213* @param key the key whose presence in this map is to be tested214* @return {@code true} if this map contains a mapping for the specified215* key216*/217public boolean containsKey(Object key) {218return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;219}220221private boolean containsMapping(Object key, Object value) {222return isValidKey(key) &&223maskNull(value).equals(vals[((Enum<?>)key).ordinal()]);224}225226/**227* Returns the value to which the specified key is mapped,228* or {@code null} if this map contains no mapping for the key.229*230* <p>More formally, if this map contains a mapping from a key231* {@code k} to a value {@code v} such that {@code (key == k)},232* then this method returns {@code v}; otherwise it returns233* {@code null}. (There can be at most one such mapping.)234*235* <p>A return value of {@code null} does not <i>necessarily</i>236* indicate that the map contains no mapping for the key; it's also237* possible that the map explicitly maps the key to {@code null}.238* The {@link #containsKey containsKey} operation may be used to239* distinguish these two cases.240*/241public V get(Object key) {242return (isValidKey(key) ?243unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);244}245246// Modification Operations247248/**249* Associates the specified value with the specified key in this map.250* If the map previously contained a mapping for this key, the old251* value is replaced.252*253* @param key the key with which the specified value is to be associated254* @param value the value to be associated with the specified key255*256* @return the previous value associated with specified key, or257* {@code null} if there was no mapping for key. (A {@code null}258* return can also indicate that the map previously associated259* {@code null} with the specified key.)260* @throws NullPointerException if the specified key is null261*/262public V put(K key, V value) {263typeCheck(key);264265int index = key.ordinal();266Object oldValue = vals[index];267vals[index] = maskNull(value);268if (oldValue == null)269size++;270return unmaskNull(oldValue);271}272273/**274* Removes the mapping for this key from this map if present.275*276* @param key the key whose mapping is to be removed from the map277* @return the previous value associated with specified key, or278* {@code null} if there was no entry for key. (A {@code null}279* return can also indicate that the map previously associated280* {@code null} with the specified key.)281*/282public V remove(Object key) {283if (!isValidKey(key))284return null;285int index = ((Enum<?>)key).ordinal();286Object oldValue = vals[index];287vals[index] = null;288if (oldValue != null)289size--;290return unmaskNull(oldValue);291}292293private boolean removeMapping(Object key, Object value) {294if (!isValidKey(key))295return false;296int index = ((Enum<?>)key).ordinal();297if (maskNull(value).equals(vals[index])) {298vals[index] = null;299size--;300return true;301}302return false;303}304305/**306* Returns true if key is of the proper type to be a key in this307* enum map.308*/309private boolean isValidKey(Object key) {310if (key == null)311return false;312313// Cheaper than instanceof Enum followed by getDeclaringClass314Class<?> keyClass = key.getClass();315return keyClass == keyType || keyClass.getSuperclass() == keyType;316}317318// Bulk Operations319320/**321* Copies all of the mappings from the specified map to this map.322* These mappings will replace any mappings that this map had for323* any of the keys currently in the specified map.324*325* @param m the mappings to be stored in this map326* @throws NullPointerException the specified map is null, or if327* one or more keys in the specified map are null328*/329public void putAll(Map<? extends K, ? extends V> m) {330if (m instanceof EnumMap<?, ?> em) {331if (em.keyType != keyType) {332if (em.isEmpty())333return;334throw new ClassCastException(em.keyType + " != " + keyType);335}336337for (int i = 0; i < keyUniverse.length; i++) {338Object emValue = em.vals[i];339if (emValue != null) {340if (vals[i] == null)341size++;342vals[i] = emValue;343}344}345} else {346super.putAll(m);347}348}349350/**351* Removes all mappings from this map.352*/353public void clear() {354Arrays.fill(vals, null);355size = 0;356}357358// Views359360/**361* This field is initialized to contain an instance of the entry set362* view the first time this view is requested. The view is stateless,363* so there's no reason to create more than one.364*/365private transient Set<Map.Entry<K,V>> entrySet;366367/**368* Returns a {@link Set} view of the keys contained in this map.369* The returned set obeys the general contract outlined in370* {@link Map#keySet()}. The set's iterator will return the keys371* in their natural order (the order in which the enum constants372* are declared).373*374* @return a set view of the keys contained in this enum map375*/376public Set<K> keySet() {377Set<K> ks = keySet;378if (ks == null) {379ks = new KeySet();380keySet = ks;381}382return ks;383}384385private class KeySet extends AbstractSet<K> {386public Iterator<K> iterator() {387return new KeyIterator();388}389public int size() {390return size;391}392public boolean contains(Object o) {393return containsKey(o);394}395public boolean remove(Object o) {396int oldSize = size;397EnumMap.this.remove(o);398return size != oldSize;399}400public void clear() {401EnumMap.this.clear();402}403}404405/**406* Returns a {@link Collection} view of the values contained in this map.407* The returned collection obeys the general contract outlined in408* {@link Map#values()}. The collection's iterator will return the409* values in the order their corresponding keys appear in map,410* which is their natural order (the order in which the enum constants411* are declared).412*413* @return a collection view of the values contained in this map414*/415public Collection<V> values() {416Collection<V> vs = values;417if (vs == null) {418vs = new Values();419values = vs;420}421return vs;422}423424private class Values extends AbstractCollection<V> {425public Iterator<V> iterator() {426return new ValueIterator();427}428public int size() {429return size;430}431public boolean contains(Object o) {432return containsValue(o);433}434public boolean remove(Object o) {435o = maskNull(o);436437for (int i = 0; i < vals.length; i++) {438if (o.equals(vals[i])) {439vals[i] = null;440size--;441return true;442}443}444return false;445}446public void clear() {447EnumMap.this.clear();448}449}450451/**452* Returns a {@link Set} view of the mappings contained in this map.453* The returned set obeys the general contract outlined in454* {@link Map#keySet()}. The set's iterator will return the455* mappings in the order their keys appear in map, which is their456* natural order (the order in which the enum constants are declared).457*458* @return a set view of the mappings contained in this enum map459*/460public Set<Map.Entry<K,V>> entrySet() {461Set<Map.Entry<K,V>> es = entrySet;462if (es != null)463return es;464else465return entrySet = new EntrySet();466}467468private class EntrySet extends AbstractSet<Map.Entry<K,V>> {469public Iterator<Map.Entry<K,V>> iterator() {470return new EntryIterator();471}472473public boolean contains(Object o) {474return o instanceof Map.Entry<?, ?> entry475&& containsMapping(entry.getKey(), entry.getValue());476}477public boolean remove(Object o) {478return o instanceof Map.Entry<?, ?> entry479&& removeMapping(entry.getKey(), entry.getValue());480}481public int size() {482return size;483}484public void clear() {485EnumMap.this.clear();486}487public Object[] toArray() {488return fillEntryArray(new Object[size]);489}490@SuppressWarnings("unchecked")491public <T> T[] toArray(T[] a) {492int size = size();493if (a.length < size)494a = (T[])java.lang.reflect.Array495.newInstance(a.getClass().getComponentType(), size);496if (a.length > size)497a[size] = null;498return (T[]) fillEntryArray(a);499}500private Object[] fillEntryArray(Object[] a) {501int j = 0;502for (int i = 0; i < vals.length; i++)503if (vals[i] != null)504a[j++] = new AbstractMap.SimpleEntry<>(505keyUniverse[i], unmaskNull(vals[i]));506return a;507}508}509510private abstract class EnumMapIterator<T> implements Iterator<T> {511// Lower bound on index of next element to return512int index = 0;513514// Index of last returned element, or -1 if none515int lastReturnedIndex = -1;516517public boolean hasNext() {518while (index < vals.length && vals[index] == null)519index++;520return index != vals.length;521}522523public void remove() {524checkLastReturnedIndex();525526if (vals[lastReturnedIndex] != null) {527vals[lastReturnedIndex] = null;528size--;529}530lastReturnedIndex = -1;531}532533private void checkLastReturnedIndex() {534if (lastReturnedIndex < 0)535throw new IllegalStateException();536}537}538539private class KeyIterator extends EnumMapIterator<K> {540public K next() {541if (!hasNext())542throw new NoSuchElementException();543lastReturnedIndex = index++;544return keyUniverse[lastReturnedIndex];545}546}547548private class ValueIterator extends EnumMapIterator<V> {549public V next() {550if (!hasNext())551throw new NoSuchElementException();552lastReturnedIndex = index++;553return unmaskNull(vals[lastReturnedIndex]);554}555}556557private class EntryIterator extends EnumMapIterator<Map.Entry<K,V>> {558private Entry lastReturnedEntry;559560public Map.Entry<K,V> next() {561if (!hasNext())562throw new NoSuchElementException();563lastReturnedEntry = new Entry(index++);564return lastReturnedEntry;565}566567public void remove() {568lastReturnedIndex =569((null == lastReturnedEntry) ? -1 : lastReturnedEntry.index);570super.remove();571lastReturnedEntry.index = lastReturnedIndex;572lastReturnedEntry = null;573}574575private class Entry implements Map.Entry<K,V> {576private int index;577578private Entry(int index) {579this.index = index;580}581582public K getKey() {583checkIndexForEntryUse();584return keyUniverse[index];585}586587public V getValue() {588checkIndexForEntryUse();589return unmaskNull(vals[index]);590}591592public V setValue(V value) {593checkIndexForEntryUse();594V oldValue = unmaskNull(vals[index]);595vals[index] = maskNull(value);596return oldValue;597}598599public boolean equals(Object o) {600if (index < 0)601return o == this;602603if (!(o instanceof Map.Entry<?, ?> e))604return false;605606V ourValue = unmaskNull(vals[index]);607Object hisValue = e.getValue();608return (e.getKey() == keyUniverse[index] &&609(ourValue == hisValue ||610(ourValue != null && ourValue.equals(hisValue))));611}612613public int hashCode() {614if (index < 0)615return super.hashCode();616617return entryHashCode(index);618}619620public String toString() {621if (index < 0)622return super.toString();623624return keyUniverse[index] + "="625+ unmaskNull(vals[index]);626}627628private void checkIndexForEntryUse() {629if (index < 0)630throw new IllegalStateException("Entry was removed");631}632}633}634635// Comparison and hashing636637/**638* Compares the specified object with this map for equality. Returns639* {@code true} if the given object is also a map and the two maps640* represent the same mappings, as specified in the {@link641* Map#equals(Object)} contract.642*643* @param o the object to be compared for equality with this map644* @return {@code true} if the specified object is equal to this map645*/646public boolean equals(Object o) {647if (this == o)648return true;649if (o instanceof EnumMap)650return equals((EnumMap<?,?>)o);651if (!(o instanceof Map<?, ?> m))652return false;653654if (size != m.size())655return false;656657for (int i = 0; i < keyUniverse.length; i++) {658if (null != vals[i]) {659K key = keyUniverse[i];660V value = unmaskNull(vals[i]);661if (null == value) {662if (!((null == m.get(key)) && m.containsKey(key)))663return false;664} else {665if (!value.equals(m.get(key)))666return false;667}668}669}670671return true;672}673674private boolean equals(EnumMap<?,?> em) {675if (em.size != size)676return false;677678if (em.keyType != keyType)679return size == 0;680681// Key types match, compare each value682for (int i = 0; i < keyUniverse.length; i++) {683Object ourValue = vals[i];684Object hisValue = em.vals[i];685if (hisValue != ourValue &&686(hisValue == null || !hisValue.equals(ourValue)))687return false;688}689return true;690}691692/**693* Returns the hash code value for this map. The hash code of a map is694* defined to be the sum of the hash codes of each entry in the map.695*/696public int hashCode() {697int h = 0;698699for (int i = 0; i < keyUniverse.length; i++) {700if (null != vals[i]) {701h += entryHashCode(i);702}703}704705return h;706}707708private int entryHashCode(int index) {709return (keyUniverse[index].hashCode() ^ vals[index].hashCode());710}711712/**713* Returns a shallow copy of this enum map. The values themselves714* are not cloned.715*716* @return a shallow copy of this enum map717*/718@SuppressWarnings("unchecked")719public EnumMap<K, V> clone() {720EnumMap<K, V> result = null;721try {722result = (EnumMap<K, V>) super.clone();723} catch(CloneNotSupportedException e) {724throw new AssertionError();725}726result.vals = result.vals.clone();727result.entrySet = null;728return result;729}730731/**732* Throws an exception if e is not of the correct type for this enum set.733*/734private void typeCheck(K key) {735Class<?> keyClass = key.getClass();736if (keyClass != keyType && keyClass.getSuperclass() != keyType)737throw new ClassCastException(keyClass + " != " + keyType);738}739740/**741* Returns all of the values comprising K.742* The result is uncloned, cached, and shared by all callers.743*/744private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {745return SharedSecrets.getJavaLangAccess()746.getEnumConstantsShared(keyType);747}748749@java.io.Serial750private static final long serialVersionUID = 458661240069192865L;751752/**753* Save the state of the {@code EnumMap} instance to a stream (i.e.,754* serialize it).755*756* @serialData The <i>size</i> of the enum map (the number of key-value757* mappings) is emitted (int), followed by the key (Object)758* and value (Object) for each key-value mapping represented759* by the enum map.760*/761@java.io.Serial762private void writeObject(java.io.ObjectOutputStream s)763throws java.io.IOException764{765// Write out the key type and any hidden stuff766s.defaultWriteObject();767768// Write out size (number of Mappings)769s.writeInt(size);770771// Write out keys and values (alternating)772int entriesToBeWritten = size;773for (int i = 0; entriesToBeWritten > 0; i++) {774if (null != vals[i]) {775s.writeObject(keyUniverse[i]);776s.writeObject(unmaskNull(vals[i]));777entriesToBeWritten--;778}779}780}781782/**783* Reconstitute the {@code EnumMap} instance from a stream (i.e.,784* deserialize it).785*/786@SuppressWarnings("unchecked")787@java.io.Serial788private void readObject(java.io.ObjectInputStream s)789throws java.io.IOException, ClassNotFoundException790{791// Read in the key type and any hidden stuff792s.defaultReadObject();793794keyUniverse = getKeyUniverse(keyType);795vals = new Object[keyUniverse.length];796797// Read in size (number of Mappings)798int size = s.readInt();799800// Read the keys and values, and put the mappings in the HashMap801for (int i = 0; i < size; i++) {802K key = (K) s.readObject();803V value = (V) s.readObject();804put(key, value);805}806}807}808809810