Java-Collections Framework学习与总结-EnumMap

来源:互联网 发布:vc网络验证系列开发 编辑:程序博客网 时间:2024/04/28 21:20
看这个类之前,先看一下Java的枚举——Enum。
        枚举是啥?编码时我们经常需要写int型常量,在某些情况下(不是全部),用枚举更合适。举个例子,在程序中需要两个常量表示性别(男、女)。可能刚开始(JDK1.5之前)我们会这样写:
Java代码  收藏代码
  1. /** 
  2.  * 男 
  3.  */  
  4. public static final int MALE = 1;  
  5.   
  6. /** 
  7.  * 女 
  8.  */  
  9. public static final int FEMALE = 0;  

        当然这么写也没什么错误,还算一目了然,用起来也还算方便。但仔细想一下这样会有什么问题!首先,假如我们有一个方法需要传一个表示男女的参数,方法内部根据参数做一些逻辑。如下:
Java代码  收藏代码
  1. public void doSthBySex(int sex){  
  2.     //...  
  3. }  

        这个方法的意图是根据性别做一些事情。但一个巨大的问题就是参数类型!应该传一个表示性别的类型,这里却是int类型。这种情况下,就算你把参数名称描述的很清晰,把方法注释写的巨牛逼,但用你方法的人照样可以无视一切,给你传个2!这下你2了,方法里面加判断吧。抛个运行时异常,说你的方法暂不支持人妖等其他性别。。。这个问题的根源所在就是int型的常量类型不安全的。
        当然你还可能希望你的MALE常量可以打印,print(MALE)一下,输出的是"MALE"而不是一个"1"(尽管这个表示男性也很形象 )。

        怎么来解决以上的问题呢,也许你又在想,Java是面向对象的吗,搞个对象不就行了。定义一个类型来表示性别,只提供两种实例表示男女,这个类还不能被扩展等等。如下:
Java代码  收藏代码
  1. public final class Sex {  
  2.       
  3.     private int type;  
  4.       
  5.     private String name;  
  6.   
  7.     private Sex(int type,String name) {  
  8.         this.type = type;  
  9.         this.name = name;  
  10.     }  
  11.       
  12.     /** 
  13.      * 男性 
  14.      */  
  15.     public static final Sex MALE = new Sex(1"MALE");  
  16.     /** 
  17.      * 女性 
  18.      */  
  19.     public static final Sex FEMALE = new Sex(0"FEMALE");  
  20.       
  21.     public int getType() {  
  22.         return type;  
  23.     }  
  24.   
  25.     public String getName() {  
  26.         return name;  
  27.     }  
  28.       
  29.     public String toString(){  
  30.         return name;  
  31.     }  
  32.   
  33. }  

        这么写已经基本可以解决类型安全,输出有意义的问题了。但是还有几个小问题。首先,如果我们只写一个简单的常量的话,这样写代码有点多(和枚举比较),当然如果你有时间的话,这都不是问题;其次,如果要遍历这类常量的话(看看Sex里有几种性别),还需要添加额外的外码;最后,其实类型安全问题还是没有完全解决,调用者照样可以利用反射创建一个"人妖"实例。
        其实枚举(Enum)做的也是上面的工作,它还做了更多的工作,从很基础的层面提供了支持。我们用枚举写一下上面的例子:
Java代码  收藏代码
  1. public enum Sex {  
  2.       
  3.     /** 
  4.      * 女性 
  5.      */  
  6.     FEMALE(0),  
  7.       
  8.     /** 
  9.      * 男性 
  10.      */  
  11.     MALE(1);  
  12.   
  13.     private int type;  
  14.       
  15.     private Sex(int type) {  
  16.         this.type = type;  
  17.     }  
  18.   
  19.     public int getType() {  
  20.         return type;  
  21.     }  
  22.       
  23. }  

        是不是比上面的例子代码少一点点,也直观一些。当然这里仅仅因为要打印的内容正好是常量的字面量(如:MALE),如果要打印"男性"的话,就得在加一个name属性了。而且我们一眼看上去,它是一个Enum类型,也就知道这个类的意图了。
        JDK中和枚举对应的类是java.lang.Enum。来看一下源代码。
Java代码  收藏代码
  1. /** 
  2.  * This is the common base class of all Java language enumeration types. 
  3.  * 
  4.  * @author  Josh Bloch 
  5.  * @author  Neal Gafter 
  6.  * @version %I%, %G% 
  7.  * @since   1.5 
  8.  */  
  9. public abstract class Enum<E extends Enum<E>>  
  10.         implements Comparable<E>, Serializable {  
  11.     /** 
  12.      * The name of this enum constant, as declared in the enum declaration. 
  13.      * Most programmers should use the {@link #toString} method rather than 
  14.      * accessing this field. 
  15.      */  
  16.     private final String name;  
  17.   
  18.     /** 
  19.      * Returns the name of this enum constant, exactly as declared in its 
  20.      * enum declaration. 
  21.      *  
  22.      * <b>Most programmers should use the {@link #toString} method in 
  23.      * preference to this one, as the toString method may return 
  24.      * a more user-friendly name.</b>  This method is designed primarily for 
  25.      * use in specialized situations where correctness depends on getting the 
  26.      * exact name, which will not vary from release to release. 
  27.      * 
  28.      * @return the name of this enum constant 
  29.      */  
  30.     public final String name() {  
  31.     return name;  
  32.     }  
  33.   
  34.     /** 
  35.      * The ordinal of this enumeration constant (its position 
  36.      * in the enum declaration, where the initial constant is assigned 
  37.      * an ordinal of zero). 
  38.      *  
  39.      * Most programmers will have no use for this field.  It is designed 
  40.      * for use by sophisticated enum-based data structures, such as 
  41.      * {@link java.util.EnumSet} and {@link java.util.EnumMap}. 
  42.      */  
  43.     private final int ordinal;  
  44.   
  45.     /** 
  46.      * Returns the ordinal of this enumeration constant (its position 
  47.      * in its enum declaration, where the initial constant is assigned 
  48.      * an ordinal of zero). 
  49.      *  
  50.      * Most programmers will have no use for this method.  It is 
  51.      * designed for use by sophisticated enum-based data structures, such 
  52.      * as {@link java.util.EnumSet} and {@link java.util.EnumMap}. 
  53.      * 
  54.      * @return the ordinal of this enumeration constant 
  55.      */  
  56.     public final int ordinal() {  
  57.     return ordinal;  
  58.     }  
  59.   
  60.     /** 
  61.      * Sole constructor.  Programmers cannot invoke this constructor. 
  62.      * It is for use by code emitted by the compiler in response to 
  63.      * enum type declarations. 
  64.      * 
  65.      * @param name - The name of this enum constant, which is the identifier 
  66.      *               used to declare it. 
  67.      * @param ordinal - The ordinal of this enumeration constant (its position 
  68.      *         in the enum declaration, where the initial constant is assigned 
  69.      *         an ordinal of zero). 
  70.      */  
  71.     protected Enum(String name, int ordinal) {  
  72.     this.name = name;  
  73.     this.ordinal = ordinal;  
  74.     }  
  75.   
  76.     /** 
  77.      * Returns the name of this enum constant, as contained in the 
  78.      * declaration.  This method may be overridden, though it typically 
  79.      * isn't necessary or desirable.  An enum type should override this 
  80.      * method when a more "programmer-friendly" string form exists. 
  81.      * 
  82.      * @return the name of this enum constant 
  83.      */  
  84.     public String toString() {  
  85.     return name;  
  86.     }  
  87.   
  88.     /** 
  89.      * Returns true if the specified object is equal to this 
  90.      * enum constant. 
  91.      * 
  92.      * @param other the object to be compared for equality with this object. 
  93.      * @return  true if the specified object is equal to this 
  94.      *          enum constant. 
  95.      */  
  96.     public final boolean equals(Object other) {   
  97.         return this==other;  
  98.     }  
  99.   
  100.     /** 
  101.      * Returns a hash code for this enum constant. 
  102.      * 
  103.      * @return a hash code for this enum constant. 
  104.      */  
  105.     public final int hashCode() {  
  106.         return super.hashCode();  
  107.     }  
  108.   
  109.     /** 
  110.      * Throws CloneNotSupportedException.  This guarantees that enums 
  111.      * are never cloned, which is necessary to preserve their "singleton" 
  112.      * status. 
  113.      * 
  114.      * @return (never returns) 
  115.      */  
  116.     protected final Object clone() throws CloneNotSupportedException {  
  117.     throw new CloneNotSupportedException();  
  118.     }  
  119.   
  120.     /** 
  121.      * Compares this enum with the specified object for order.  Returns a 
  122.      * negative integer, zero, or a positive integer as this object is less 
  123.      * than, equal to, or greater than the specified object. 
  124.      *  
  125.      * Enum constants are only comparable to other enum constants of the 
  126.      * same enum type.  The natural order implemented by this 
  127.      * method is the order in which the constants are declared. 
  128.      */  
  129.     public final int compareTo(E o) {  
  130.     Enum other = (Enum)o;  
  131.     Enum self = this;  
  132.     if (self.getClass() != other.getClass() && // optimization  
  133.             self.getDeclaringClass() != other.getDeclaringClass())  
  134.         throw new ClassCastException();  
  135.     return self.ordinal - other.ordinal;  
  136.     }  
  137.   
  138.     /** 
  139.      * Returns the Class object corresponding to this enum constant's 
  140.      * enum type.  Two enum constants e1 and  e2 are of the 
  141.      * same enum type if and only if 
  142.      *   e1.getDeclaringClass() == e2.getDeclaringClass(). 
  143.      * (The value returned by this method may differ from the one returned 
  144.      * by the {@link Object#getClass} method for enum constants with 
  145.      * constant-specific class bodies.) 
  146.      * 
  147.      * @return the Class object corresponding to this enum constant's 
  148.      *     enum type 
  149.      */  
  150.     public final Class<E> getDeclaringClass() {  
  151.     Class clazz = getClass();  
  152.     Class zuper = clazz.getSuperclass();  
  153.     return (zuper == Enum.class) ? clazz : zuper;  
  154.     }  
  155.   
  156.     /** 
  157.      * Returns the enum constant of the specified enum type with the 
  158.      * specified name.  The name must match exactly an identifier used 
  159.      * to declare an enum constant in this type.  (Extraneous whitespace 
  160.      * characters are not permitted.)  
  161.      * 
  162.      * @param enumType the <tt>Class</tt> object of the enum type from which 
  163.      *      to return a constant 
  164.      * @param name the name of the constant to return 
  165.      * @return the enum constant of the specified enum type with the 
  166.      *      specified name 
  167.      * @throws IllegalArgumentException if the specified enum type has 
  168.      *         no constant with the specified name, or the specified 
  169.      *         class object does not represent an enum type 
  170.      * @throws NullPointerException if <tt>enumType</tt> or <tt>name</tt> 
  171.      *         is null 
  172.      * @since 1.5 
  173.      */  
  174.     public static <T extends Enum<T>> T valueOf(Class<T> enumType,  
  175.                                                 String name) {  
  176.         T result = enumType.enumConstantDirectory().get(name);  
  177.         if (result != null)  
  178.             return result;  
  179.         if (name == null)  
  180.             throw new NullPointerException("Name is null");  
  181.         throw new IllegalArgumentException(  
  182.             "No enum const " + enumType +"." + name);  
  183.     }  
  184.   
  185.     /** 
  186.       * prevent default deserialization 
  187.       */  
  188.     private void readObject(ObjectInputStream in) throws IOException,  
  189.         ClassNotFoundException {  
  190.             throw new InvalidObjectException("can't deserialize enum");  
  191.     }  
  192.   
  193.     private void readObjectNoData() throws ObjectStreamException {  
  194.         throw new InvalidObjectException("can't deserialize enum");  
  195.     }  
  196.   
  197.     /** 
  198.      * enum classes cannot have finalize methods. 
  199.      */  
  200.     protected final void finalize() { }  
  201. }  

        枚举的内部有2个属性。一个是name,表示枚举具体实例的名称。另一个是ordinal表示枚举实例的序数(枚举实例在定义时出现的顺序,从0开始)。从ordinal的注释也能看到,这个属性一般不会被外部程序用到,只有一些基于枚举的数据结构(如EnumSet和EnumMap这两货)中会用到。

        枚举内容简单总结到这儿,下面看一下java.util.EnumMap吧。直接看源码:
Java代码  收藏代码
  1. public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>  
  2.     implements java.io.Serializable, Cloneable  
  3. {  

        和其他的Map实现一样,也扩展自AbstractMap,可序列化可克隆。注意Key的类型声明,必须是Enum的子类。继续往下看。
Java代码  收藏代码
  1. /** 
  2.  * The <tt>Class</tt> object for the enum type of all the keys of this map. 
  3.  * 
  4.  * @serial 
  5.  */  
  6. private final Class<K> keyType;  
  7.   
  8. /** 
  9.  * All of the values comprising K.  (Cached for performance.) 
  10.  */  
  11. private transient K[] keyUniverse;  
  12.   
  13. /** 
  14.  * Array representation of this map.  The ith element is the value 
  15.  * to which universe[i] is currently mapped, or null if it isn't 
  16.  * mapped to anything, or NULL if it's mapped to null. 
  17.  */  
  18. private transient Object[] vals;  
  19.   
  20. /** 
  21.  * The number of mappings in this map. 
  22.  */  
  23. private transient int size = 0;  

        keyType保存Key的类型;keyUniverse保存Key,vals保存对应的Value。通过注释可以知道Key和Value是通过下标来映射的,不同于其他Map实现,这里没有散列表等等复杂的数据结构,只有两个数组,通过下标对应。这样的实现无疑是高效快速的。
Java代码  收藏代码
  1. /** 
  2.  * Creates an empty enum map with the specified key type. 
  3.  * 
  4.  * @param keyType the class object of the key type for this enum map 
  5.  * @throws NullPointerException if <tt>keyType</tt> is null 
  6.  */  
  7. public EnumMap(Class<K> keyType) {  
  8.     this.keyType = keyType;  
  9.     keyUniverse = getKeyUniverse(keyType);  
  10.     vals = new Object[keyUniverse.length];  
  11. }  
  12.   
  13. /** 
  14.  * Creates an enum map with the same key type as the specified enum 
  15.  * map, initially containing the same mappings (if any). 
  16.  * 
  17.  * @param m the enum map from which to initialize this enum map 
  18.  * @throws NullPointerException if <tt>m</tt> is null 
  19.  */  
  20. public EnumMap(EnumMap<K, ? extends V> m) {  
  21.     keyType = m.keyType;  
  22.     keyUniverse = m.keyUniverse;  
  23.     vals = (Object[]) m.vals.clone();  
  24.     size = m.size;  
  25. }  
  26.   
  27. /** 
  28.  * Creates an enum map initialized from the specified map.  If the 
  29.  * specified map is an <tt>EnumMap</tt> instance, this constructor behaves 
  30.  * identically to {@link #EnumMap(EnumMap)}.  Otherwise, the specified map 
  31.  * must contain at least one mapping (in order to determine the new 
  32.  * enum map's key type). 
  33.  * 
  34.  * @param m the map from which to initialize this enum map 
  35.  * @throws IllegalArgumentException if <tt>m</tt> is not an 
  36.  *     <tt>EnumMap</tt> instance and contains no mappings 
  37.  * @throws NullPointerException if <tt>m</tt> is null 
  38.  */  
  39. public EnumMap(Map<K, ? extends V> m) {  
  40.     if (m instanceof EnumMap) {  
  41.         EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;  
  42.         keyType = em.keyType;  
  43.         keyUniverse = em.keyUniverse;  
  44.         vals = (Object[]) em.vals.clone();  
  45.         size = em.size;  
  46.     } else {  
  47.         if (m.isEmpty())  
  48.             throw new IllegalArgumentException("Specified map is empty");  
  49.         keyType = m.keySet().iterator().next().getDeclaringClass();  
  50.         keyUniverse = getKeyUniverse(keyType);  
  51.         vals = new Object[keyUniverse.length];  
  52.         putAll(m);  
  53.     }  
  54. }  
  55.   
  56. /** 
  57.  * Returns all of the values comprising K. 
  58.  * The result is uncloned, cached, and shared by all callers. 
  59.  */  
  60. private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {  
  61.     return SharedSecrets.getJavaLangAccess()  
  62.     .getEnumConstantsShared(keyType);  
  63. }  

        构造方法中会通过keyType调用一个getKeyUniverse方法来获取所有的枚举实例,并放到keyUniverse中缓存起来。
        下面看一个关键的方法——put方法。
Java代码  收藏代码
  1. /** 
  2.  * Associates the specified value with the specified key in this map. 
  3.  * If the map previously contained a mapping for this key, the old 
  4.  * value is replaced. 
  5.  * 
  6.  * @param key the key with which the specified value is to be associated 
  7.  * @param value the value to be associated with the specified key 
  8.  * 
  9.  * @return the previous value associated with specified key, or 
  10.  *     <tt>null</tt> if there was no mapping for key.  (A <tt>null</tt> 
  11.  *     return can also indicate that the map previously associated 
  12.  *     <tt>null</tt> with the specified key.) 
  13.  * @throws NullPointerException if the specified key is null 
  14.  */  
  15. public V put(K key, V value) {  
  16.     typeCheck(key);  
  17.   
  18.     int index = ((Enum)key).ordinal();  
  19.     Object oldValue = vals[index];  
  20.     vals[index] = maskNull(value);  
  21.     if (oldValue == null)  
  22.         size++;  
  23.     return unmaskNull(oldValue);  
  24. }  

        可以看到,用来映射Key和Value的下标就是Enum的ordinal。这样做即可以保证映射的正确性,又能保证遍历顺序和Enum定义的顺序一致。
        其他方法大概看一下,没有复杂的数据结构,都很容易看懂。
Java代码  收藏代码
  1. /** 
  2.  * Returns the value to which the specified key is mapped, 
  3.  * or {@code null} if this map contains no mapping for the key. 
  4.  * 
  5.  * <p>More formally, if this map contains a mapping from a key 
  6.  * {@code k} to a value {@code v} such that {@code (key == k)}, 
  7.  * then this method returns {@code v}; otherwise it returns 
  8.  * {@code null}.  (There can be at most one such mapping.) 
  9.  * 
  10.  * <p>A return value of {@code null} does not <i>necessarily</i> 
  11.  * indicate that the map contains no mapping for the key; it's also 
  12.  * possible that the map explicitly maps the key to {@code null}. 
  13.  * The {@link #containsKey containsKey} operation may be used to 
  14.  * distinguish these two cases. 
  15.  */  
  16. public V get(Object key) {  
  17.     return (isValidKey(key) ?  
  18.             unmaskNull(vals[((Enum)key).ordinal()]) : null);  
  19. }  
  20.   
  21. /** 
  22.  * Removes the mapping for this key from this map if present. 
  23.  * 
  24.  * @param key the key whose mapping is to be removed from the map 
  25.  * @return the previous value associated with specified key, or 
  26.  *     <tt>null</tt> if there was no entry for key.  (A <tt>null</tt> 
  27.  *     return can also indicate that the map previously associated 
  28.  *     <tt>null</tt> with the specified key.) 
  29.  */  
  30. public V remove(Object key) {  
  31.     if (!isValidKey(key))  
  32.         return null;  
  33.     int index = ((Enum)key).ordinal();  
  34.     Object oldValue = vals[index];  
  35.     vals[index] = null;  
  36.     if (oldValue != null)  
  37.         size--;  
  38.     return unmaskNull(oldValue);  
  39. }  

        总之,当我们要使用以枚举为Key的Map时,使用这个再合适不过了。当然也可以使用其他实现如HashMap等,但无疑在这种场景下,EnumMap更加高效。
0 0