背景
想必使用Java5之后枚举已经是每一个Java开发者很常用的工具了。特别是用来实现单例模式通过枚举也是一种推荐的方式。
大部分开发者也有根据枚举回去Map的需求。那么Jdk也提供了EnumMap这种便捷的工具类。
EnumMap内部通过数组【特定大小】比较高效和避免了冲突的方法完成更加方便安全和快捷的完成了枚举的map的构建。
源码
类图
构造函数
/** * Creates an empty enum map with the specified key type. * * @param keyType the class object of the key type for this enum map * @throws NullPointerException if <tt>keyType</tt> is null */ public EnumMap(Class<K> keyType) { this.keyType = keyType; keyUniverse = getKeyUniverse(keyType); vals = new Object[keyUniverse.length]; } /** * Creates an enum map with the same key type as the specified enum * map, initially containing the same mappings (if any). * * @param m the enum map from which to initialize this enum map * @throws NullPointerException if <tt>m</tt> is null */ public EnumMap(EnumMap<K, ? extends V> m) { keyType = m.keyType; keyUniverse = m.keyUniverse; vals = m.vals.clone(); size = m.size; }
通常来说我们传递一个枚举类作为构造函数的参数。
很明显EnumMap 使用了和枚举数目长度大小相同的数组
put
// Modification Operations /** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for this key, the old * value is replaced. * * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key * * @return the previous value associated with specified key, or * <tt>null</tt> if there was no mapping for key. (A <tt>null</tt> * return can also indicate that the map previously associated * <tt>null</tt> with the specified key.) * @throws NullPointerException if the specified key is null */ public V put(K key, V value) { typeCheck(key); int index = key.ordinal(); Object oldValue = vals[index]; vals[index] = maskNull(value); if (oldValue == null) size++; return unmaskNull(oldValue); } /** * Throws an exception if e is not of the correct type for this enum set. */ private void typeCheck(K key) { Class keyClass = key.getClass(); if (keyClass != keyType && keyClass.getSuperclass() != keyType) throw new ClassCastException(keyClass + " != " + keyType); }
相比较其他map enumMap对key进行了类型检查 如果类型不对则直接抛出异常。
其次就是相比其他Map内存能够降低的关键
EnumMap使用了key.ordinal()作为数组下标【由于枚举的数量是有限的,因此在初始化的时候EnumMap就已经初始化了和枚举长度相同的数组】
直接以下标的形式进行读写 因此其内存占用率相比其他Map是比较低的,并没有存key~
当然需要注意一下如果value是null的话EnumMap使用了一个特殊的对象NULL
用这个对象可以用来区分是否包含对应的key
比如开发者调用put(Enum.XXX,null)
但是如果直接把null放入数组那么后面containsKey就无法判断了
get
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key == k)}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. */ public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum)key).ordinal()]) : null); } /** * Returns true if key is of the proper type to be a key in this * enum map. */ private boolean isValidKey(Object key) { if (key == null) return false; // Cheaper than instanceof Enum followed by getDeclaringClass Class keyClass = key.getClass(); return keyClass == keyType || keyClass.getSuperclass() == keyType; }
由于实现了Map的get方法因此不能使用泛型为Object 这个在编译器就不会报错。
因此建议可以在使用之前由开发者自己判断一次类型
remove
/** * Removes the mapping for this key from this map if present. * * @param key the key whose mapping is to be removed from the map * @return the previous value associated with specified key, or * <tt>null</tt> if there was no entry for key. (A <tt>null</tt> * return can also indicate that the map previously associated * <tt>null</tt> with the specified key.) */ public V remove(Object key) { if (!isValidKey(key)) return null; int index = ((Enum)key).ordinal(); Object oldValue = vals[index]; vals[index] = null; if (oldValue != null) size--; return unmaskNull(oldValue); }
因此都可以利用枚举的ordinal方法 仍然需要注意的是NULL
containsKey
/** * Returns <tt>true</tt> if this map contains a mapping for the specified * key. * * @param key the key whose presence in this map is to be tested * @return <tt>true</tt> if this map contains a mapping for the specified * key */ public boolean containsKey(Object key) { return isValidKey(key) && vals[((Enum)key).ordinal()] != null; }
正是由于put时value为空的时候放入了NULL对象 所以才可以用!=null 来判断是否包含key
iter
迭代器的时候则需要判断数组对应元素是否为空
private abstract class EnumMapIterator<T> implements Iterator<T> { // Lower bound on index of next element to return int index = 0; // Index of last returned element, or -1 if none int lastReturnedIndex = -1; public boolean hasNext() { while (index < vals.length && vals[index] == null) index++; return index != vals.length; } public void remove() { checkLastReturnedIndex(); if (vals[lastReturnedIndex] != null) { vals[lastReturnedIndex] = null; size--; } lastReturnedIndex = -1; } private void checkLastReturnedIndex() { if (lastReturnedIndex < 0) throw new IllegalStateException(); } }
这样就可以完成了EnumMap的迭代 和其他JCF的集合类似EnumMap也不是线程安全的,需要通过同步该集合来保证