Java-Collections Framework学习与总结-EnumMap
来源:互联网 发布:vc网络验证系列开发 编辑:程序博客网 时间:2024/04/28 21:20
看这个类之前,先看一下Java的枚举——Enum。
枚举是啥?编码时我们经常需要写int型常量,在某些情况下(不是全部),用枚举更合适。举个例子,在程序中需要两个常量表示性别(男、女)。可能刚开始(JDK1.5之前)我们会这样写:
当然这么写也没什么错误,还算一目了然,用起来也还算方便。但仔细想一下这样会有什么问题!首先,假如我们有一个方法需要传一个表示男女的参数,方法内部根据参数做一些逻辑。如下:
这个方法的意图是根据性别做一些事情。但一个巨大的问题就是参数类型!应该传一个表示性别的类型,这里却是int类型。这种情况下,就算你把参数名称描述的很清晰,把方法注释写的巨牛逼,但用你方法的人照样可以无视一切,给你传个2!这下你2了,方法里面加判断吧。抛个运行时异常,说你的方法暂不支持人妖等其他性别。。。这个问题的根源所在就是int型的常量类型不安全的。
当然你还可能希望你的MALE常量可以打印,print(MALE)一下,输出的是"MALE"而不是一个"1"(尽管这个表示男性也很形象 )。
怎么来解决以上的问题呢,也许你又在想,Java是面向对象的吗,搞个对象不就行了。定义一个类型来表示性别,只提供两种实例表示男女,这个类还不能被扩展等等。如下:
这么写已经基本可以解决类型安全,输出有意义的问题了。但是还有几个小问题。首先,如果我们只写一个简单的常量的话,这样写代码有点多(和枚举比较),当然如果你有时间的话,这都不是问题;其次,如果要遍历这类常量的话(看看Sex里有几种性别),还需要添加额外的外码;最后,其实类型安全问题还是没有完全解决,调用者照样可以利用反射创建一个"人妖"实例。
其实枚举(Enum)做的也是上面的工作,它还做了更多的工作,从很基础的层面提供了支持。我们用枚举写一下上面的例子:
是不是比上面的例子代码少一点点,也直观一些。当然这里仅仅因为要打印的内容正好是常量的字面量(如:MALE),如果要打印"男性"的话,就得在加一个name属性了。而且我们一眼看上去,它是一个Enum类型,也就知道这个类的意图了。
JDK中和枚举对应的类是java.lang.Enum。来看一下源代码。
枚举的内部有2个属性。一个是name,表示枚举具体实例的名称。另一个是ordinal表示枚举实例的序数(枚举实例在定义时出现的顺序,从0开始)。从ordinal的注释也能看到,这个属性一般不会被外部程序用到,只有一些基于枚举的数据结构(如EnumSet和EnumMap这两货)中会用到。
枚举内容简单总结到这儿,下面看一下java.util.EnumMap吧。直接看源码:
和其他的Map实现一样,也扩展自AbstractMap,可序列化可克隆。注意Key的类型声明,必须是Enum的子类。继续往下看。
keyType保存Key的类型;keyUniverse保存Key,vals保存对应的Value。通过注释可以知道Key和Value是通过下标来映射的,不同于其他Map实现,这里没有散列表等等复杂的数据结构,只有两个数组,通过下标对应。这样的实现无疑是高效快速的。
构造方法中会通过keyType调用一个getKeyUniverse方法来获取所有的枚举实例,并放到keyUniverse中缓存起来。
下面看一个关键的方法——put方法。
可以看到,用来映射Key和Value的下标就是Enum的ordinal。这样做即可以保证映射的正确性,又能保证遍历顺序和Enum定义的顺序一致。
其他方法大概看一下,没有复杂的数据结构,都很容易看懂。
总之,当我们要使用以枚举为Key的Map时,使用这个再合适不过了。当然也可以使用其他实现如HashMap等,但无疑在这种场景下,EnumMap更加高效。
枚举是啥?编码时我们经常需要写int型常量,在某些情况下(不是全部),用枚举更合适。举个例子,在程序中需要两个常量表示性别(男、女)。可能刚开始(JDK1.5之前)我们会这样写:
- /**
- * 男
- */
- public static final int MALE = 1;
- /**
- * 女
- */
- public static final int FEMALE = 0;
当然这么写也没什么错误,还算一目了然,用起来也还算方便。但仔细想一下这样会有什么问题!首先,假如我们有一个方法需要传一个表示男女的参数,方法内部根据参数做一些逻辑。如下:
- public void doSthBySex(int sex){
- //...
- }
这个方法的意图是根据性别做一些事情。但一个巨大的问题就是参数类型!应该传一个表示性别的类型,这里却是int类型。这种情况下,就算你把参数名称描述的很清晰,把方法注释写的巨牛逼,但用你方法的人照样可以无视一切,给你传个2!这下你2了,方法里面加判断吧。抛个运行时异常,说你的方法暂不支持人妖等其他性别。。。这个问题的根源所在就是int型的常量类型不安全的。
当然你还可能希望你的MALE常量可以打印,print(MALE)一下,输出的是"MALE"而不是一个"1"(尽管这个表示男性也很形象 )。
怎么来解决以上的问题呢,也许你又在想,Java是面向对象的吗,搞个对象不就行了。定义一个类型来表示性别,只提供两种实例表示男女,这个类还不能被扩展等等。如下:
- public final class Sex {
- private int type;
- private String name;
- private Sex(int type,String name) {
- this.type = type;
- this.name = name;
- }
- /**
- * 男性
- */
- public static final Sex MALE = new Sex(1, "MALE");
- /**
- * 女性
- */
- public static final Sex FEMALE = new Sex(0, "FEMALE");
- public int getType() {
- return type;
- }
- public String getName() {
- return name;
- }
- public String toString(){
- return name;
- }
- }
这么写已经基本可以解决类型安全,输出有意义的问题了。但是还有几个小问题。首先,如果我们只写一个简单的常量的话,这样写代码有点多(和枚举比较),当然如果你有时间的话,这都不是问题;其次,如果要遍历这类常量的话(看看Sex里有几种性别),还需要添加额外的外码;最后,其实类型安全问题还是没有完全解决,调用者照样可以利用反射创建一个"人妖"实例。
其实枚举(Enum)做的也是上面的工作,它还做了更多的工作,从很基础的层面提供了支持。我们用枚举写一下上面的例子:
- public enum Sex {
- /**
- * 女性
- */
- FEMALE(0),
- /**
- * 男性
- */
- MALE(1);
- private int type;
- private Sex(int type) {
- this.type = type;
- }
- public int getType() {
- return type;
- }
- }
是不是比上面的例子代码少一点点,也直观一些。当然这里仅仅因为要打印的内容正好是常量的字面量(如:MALE),如果要打印"男性"的话,就得在加一个name属性了。而且我们一眼看上去,它是一个Enum类型,也就知道这个类的意图了。
JDK中和枚举对应的类是java.lang.Enum。来看一下源代码。
- /**
- * This is the common base class of all Java language enumeration types.
- *
- * @author Josh Bloch
- * @author Neal Gafter
- * @version %I%, %G%
- * @since 1.5
- */
- public abstract class Enum<E extends Enum<E>>
- implements Comparable<E>, Serializable {
- /**
- * The name of this enum constant, as declared in the enum declaration.
- * Most programmers should use the {@link #toString} method rather than
- * accessing this field.
- */
- private final String name;
- /**
- * Returns the name of this enum constant, exactly as declared in its
- * enum declaration.
- *
- * <b>Most programmers should use the {@link #toString} method in
- * preference to this one, as the toString method may return
- * a more user-friendly name.</b> This method is designed primarily for
- * use in specialized situations where correctness depends on getting the
- * exact name, which will not vary from release to release.
- *
- * @return the name of this enum constant
- */
- public final String name() {
- return name;
- }
- /**
- * The ordinal of this enumeration constant (its position
- * in the enum declaration, where the initial constant is assigned
- * an ordinal of zero).
- *
- * Most programmers will have no use for this field. It is designed
- * for use by sophisticated enum-based data structures, such as
- * {@link java.util.EnumSet} and {@link java.util.EnumMap}.
- */
- private final int ordinal;
- /**
- * Returns the ordinal of this enumeration constant (its position
- * in its enum declaration, where the initial constant is assigned
- * an ordinal of zero).
- *
- * Most programmers will have no use for this method. It is
- * designed for use by sophisticated enum-based data structures, such
- * as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
- *
- * @return the ordinal of this enumeration constant
- */
- public final int ordinal() {
- return ordinal;
- }
- /**
- * Sole constructor. Programmers cannot invoke this constructor.
- * It is for use by code emitted by the compiler in response to
- * enum type declarations.
- *
- * @param name - The name of this enum constant, which is the identifier
- * used to declare it.
- * @param ordinal - The ordinal of this enumeration constant (its position
- * in the enum declaration, where the initial constant is assigned
- * an ordinal of zero).
- */
- protected Enum(String name, int ordinal) {
- this.name = name;
- this.ordinal = ordinal;
- }
- /**
- * Returns the name of this enum constant, as contained in the
- * declaration. This method may be overridden, though it typically
- * isn't necessary or desirable. An enum type should override this
- * method when a more "programmer-friendly" string form exists.
- *
- * @return the name of this enum constant
- */
- public String toString() {
- return name;
- }
- /**
- * Returns true if the specified object is equal to this
- * enum constant.
- *
- * @param other the object to be compared for equality with this object.
- * @return true if the specified object is equal to this
- * enum constant.
- */
- public final boolean equals(Object other) {
- return this==other;
- }
- /**
- * Returns a hash code for this enum constant.
- *
- * @return a hash code for this enum constant.
- */
- public final int hashCode() {
- return super.hashCode();
- }
- /**
- * Throws CloneNotSupportedException. This guarantees that enums
- * are never cloned, which is necessary to preserve their "singleton"
- * status.
- *
- * @return (never returns)
- */
- protected final Object clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
- /**
- * Compares this enum with the specified object for order. Returns a
- * negative integer, zero, or a positive integer as this object is less
- * than, equal to, or greater than the specified object.
- *
- * Enum constants are only comparable to other enum constants of the
- * same enum type. The natural order implemented by this
- * method is the order in which the constants are declared.
- */
- public final int compareTo(E o) {
- Enum other = (Enum)o;
- Enum self = this;
- if (self.getClass() != other.getClass() && // optimization
- self.getDeclaringClass() != other.getDeclaringClass())
- throw new ClassCastException();
- return self.ordinal - other.ordinal;
- }
- /**
- * Returns the Class object corresponding to this enum constant's
- * enum type. Two enum constants e1 and e2 are of the
- * same enum type if and only if
- * e1.getDeclaringClass() == e2.getDeclaringClass().
- * (The value returned by this method may differ from the one returned
- * by the {@link Object#getClass} method for enum constants with
- * constant-specific class bodies.)
- *
- * @return the Class object corresponding to this enum constant's
- * enum type
- */
- public final Class<E> getDeclaringClass() {
- Class clazz = getClass();
- Class zuper = clazz.getSuperclass();
- return (zuper == Enum.class) ? clazz : zuper;
- }
- /**
- * Returns the enum constant of the specified enum type with the
- * specified name. The name must match exactly an identifier used
- * to declare an enum constant in this type. (Extraneous whitespace
- * characters are not permitted.)
- *
- * @param enumType the <tt>Class</tt> object of the enum type from which
- * to return a constant
- * @param name the name of the constant to return
- * @return the enum constant of the specified enum type with the
- * specified name
- * @throws IllegalArgumentException if the specified enum type has
- * no constant with the specified name, or the specified
- * class object does not represent an enum type
- * @throws NullPointerException if <tt>enumType</tt> or <tt>name</tt>
- * is null
- * @since 1.5
- */
- public static <T extends Enum<T>> T valueOf(Class<T> enumType,
- String name) {
- T result = enumType.enumConstantDirectory().get(name);
- if (result != null)
- return result;
- if (name == null)
- throw new NullPointerException("Name is null");
- throw new IllegalArgumentException(
- "No enum const " + enumType +"." + name);
- }
- /**
- * prevent default deserialization
- */
- private void readObject(ObjectInputStream in) throws IOException,
- ClassNotFoundException {
- throw new InvalidObjectException("can't deserialize enum");
- }
- private void readObjectNoData() throws ObjectStreamException {
- throw new InvalidObjectException("can't deserialize enum");
- }
- /**
- * enum classes cannot have finalize methods.
- */
- protected final void finalize() { }
- }
枚举的内部有2个属性。一个是name,表示枚举具体实例的名称。另一个是ordinal表示枚举实例的序数(枚举实例在定义时出现的顺序,从0开始)。从ordinal的注释也能看到,这个属性一般不会被外部程序用到,只有一些基于枚举的数据结构(如EnumSet和EnumMap这两货)中会用到。
枚举内容简单总结到这儿,下面看一下java.util.EnumMap吧。直接看源码:
- public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
- implements java.io.Serializable, Cloneable
- {
和其他的Map实现一样,也扩展自AbstractMap,可序列化可克隆。注意Key的类型声明,必须是Enum的子类。继续往下看。
- /**
- * The <tt>Class</tt> object for the enum type of all the keys of this map.
- *
- * @serial
- */
- private final Class<K> keyType;
- /**
- * All of the values comprising K. (Cached for performance.)
- */
- private transient K[] keyUniverse;
- /**
- * Array representation of this map. The ith element is the value
- * to which universe[i] is currently mapped, or null if it isn't
- * mapped to anything, or NULL if it's mapped to null.
- */
- private transient Object[] vals;
- /**
- * The number of mappings in this map.
- */
- private transient int size = 0;
keyType保存Key的类型;keyUniverse保存Key,vals保存对应的Value。通过注释可以知道Key和Value是通过下标来映射的,不同于其他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 = (Object[]) m.vals.clone();
- size = m.size;
- }
- /**
- * Creates an enum map initialized from the specified map. If the
- * specified map is an <tt>EnumMap</tt> instance, this constructor behaves
- * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map
- * must contain at least one mapping (in order to determine the new
- * enum map's key type).
- *
- * @param m the map from which to initialize this enum map
- * @throws IllegalArgumentException if <tt>m</tt> is not an
- * <tt>EnumMap</tt> instance and contains no mappings
- * @throws NullPointerException if <tt>m</tt> is null
- */
- public EnumMap(Map<K, ? extends V> m) {
- if (m instanceof EnumMap) {
- EnumMap<K, ? extends V> em = (EnumMap<K, ? extends V>) m;
- keyType = em.keyType;
- keyUniverse = em.keyUniverse;
- vals = (Object[]) em.vals.clone();
- size = em.size;
- } else {
- if (m.isEmpty())
- throw new IllegalArgumentException("Specified map is empty");
- keyType = m.keySet().iterator().next().getDeclaringClass();
- keyUniverse = getKeyUniverse(keyType);
- vals = new Object[keyUniverse.length];
- putAll(m);
- }
- }
- /**
- * Returns all of the values comprising K.
- * The result is uncloned, cached, and shared by all callers.
- */
- private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
- return SharedSecrets.getJavaLangAccess()
- .getEnumConstantsShared(keyType);
- }
构造方法中会通过keyType调用一个getKeyUniverse方法来获取所有的枚举实例,并放到keyUniverse中缓存起来。
下面看一个关键的方法——put方法。
- /**
- * 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 = ((Enum)key).ordinal();
- Object oldValue = vals[index];
- vals[index] = maskNull(value);
- if (oldValue == null)
- size++;
- return unmaskNull(oldValue);
- }
可以看到,用来映射Key和Value的下标就是Enum的ordinal。这样做即可以保证映射的正确性,又能保证遍历顺序和Enum定义的顺序一致。
其他方法大概看一下,没有复杂的数据结构,都很容易看懂。
- /**
- * 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);
- }
- /**
- * 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);
- }
总之,当我们要使用以枚举为Key的Map时,使用这个再合适不过了。当然也可以使用其他实现如HashMap等,但无疑在这种场景下,EnumMap更加高效。
0 0
- Java-Collections Framework学习与总结-EnumMap
- Java-Collections Framework学习与总结-ArrayDeque
- Java-Collections Framework学习与总结-HashMap
- Java-Collections Framework学习与总结-PriorityQueue
- Java-Collections Framework学习与总结-WeakHashMap
- Java-Collections Framework学习与总结-IdentityHashMap
- Java-Collections Framework学习与总结-ArrayList
- Java-Collections Framework学习与总结-LinkedList
- Java-Collections Framework学习与总结-LinkedHashMap
- Java-Collections Framework学习与总结-HashSet和LinkedHashSet
- java collections framework -----List,Set,Map总结
- Java 之EnumSet与EnumMap
- Java Collections Framework
- Java Collections Framework比较
- Java Collections Framework比较
- Java Collections Framework
- Java Collections Framework
- Java Collections Framework
- Lucene4.10.3索引,使用iK分词
- Android自动化工具Appium的使用
- 三分钟理解Java继承和多态原理
- ubuntu 12.04增加tun
- coredata中的默认表的字段
- Java-Collections Framework学习与总结-EnumMap
- pat_1010
- MongoDB数据库镜像的主从配对与数据迁移示例
- Java-Collections Framework学习与总结-PriorityQueue
- MapReduce学习笔记(1)
- openstack iptables太长
- spring 源码解读与设计详解:4 DefaultListableBeanFactory及资源载入
- 一个修改时区timezone相关的shell脚本(附循环生成iplist)
- Linux中文件权限标识详解