精华内容
下载资源
问答
  • EnumMap

    2018-02-05 22:40:46
    EnumMap是Map接口的一种实现,专门用于枚举类型的键,所有枚举的键必须来自同一个枚举 EnumMap不允许键为空,允许值为空 EnumMap成员变量 private final Class keyType; private transient K[] keyUniverse; ...

    EnumMap是Map接口的一种实现,专门用于枚举类型的键,所有枚举的键必须来自同一个枚举

    EnumMap不允许键为空,允许值为空

    EnumMap成员变量

        private final Class<K> keyType;
    
        private transient K[] keyUniverse;
    
        private transient Object[] vals;
    
        private transient int size = 0;
    
        private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }
        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    其中有一个特别的数据类型它是Object实例NULL,用于取代真正的NULL值

    下面是几个常用的方法源码解读

    put

    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);
    }

    EnumMap key必须来自同一个枚举,在向EnumMap存放数据的时候,首先进行类型检查

    private void typeCheck(K key) {
        Class<?> keyClass = key.getClass();
        if (keyClass != keyType && keyClass.getSuperclass() != keyType)
            throw new ClassCastException(keyClass + " != " + keyType);
    }

    然后获取该枚举的索引值以该索引为vals数组的索引存放数据

    检查的逻辑是判断当前类和父类是否是指定的枚举类型,如果不是会抛出ClassCastException
    类型检查之后,存放数据,数据的存放在EnumMap中对null型数据有额外的操作

    private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }
    @SuppressWarnings("unchecked")
    private V unmaskNull(Object value) {
        return (V)(value == NULL ? null : value);
    }

    如果存放null值,EnumMap会将其替换成一个重写了toString和hashcode的Object
    如下代码所示

    private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }
        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    取数据的时候如果是上面的NULL,那么进行unmask操作

    get

    public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
    }

    get方法很简单,首先判断key值是否合理

    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;
    }

    判断key的类型或者key的父类型是否与指定的EnumMap的键值类型相同

    然后判断vals中的值是否为NULL,如果是进行unmask转换

    展开全文
  • EnumMap 分析

    2020-01-21 11:36:16
    EnumMap(线程不安全): // 声明时首先会再内部根据传入的枚举类型的属性长度,创建等长的数组 public EnumMap(Class<K> keyType) { this.keyType = keyType; keyUniverse = getKeyUniverse(keyType); ...

    EnumMap(线程不安全):

       // 声明时首先会再内部根据传入的枚举类型的属性长度,创建等长的数组
       public EnumMap(Class<K> keyType) {
            this.keyType = keyType;
            keyUniverse = getKeyUniverse(keyType);
            vals = new Object[keyUniverse.length];
        }
        public V put(K key, V value) {
            typeCheck(key); // 首先进行类型检查
    
            int index = key.ordinal(); // 获取枚举类型的ordinal值,直接放到数组的对应index位置
            Object oldValue = vals[index];
            vals[index] = maskNull(value);
            if (oldValue == null)
                size++;
            return unmaskNull(oldValue);
        }
        
        /**
         * 进行类型检查,如果不是添加的key的类型和声明时的类型不一致,抛出类型不一致异常
         * 如果添加的key为null,则抛出NPE;
         */
        private void typeCheck(K key) {
            Class<?> keyClass = key.getClass();
            if (keyClass != keyType && keyClass.getSuperclass() != keyType)
                throw new ClassCastException(keyClass + " != " + keyType);
        }
        
        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);
        }
    
        public boolean containsKey(Object key) {
            return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;
        }
    
        public V get(Object key) {
            return (isValidKey(key) ?
                unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);
        }

    如果想创建线程安全的EnumMap类,则可以通过如下方式:

    // EnumTest 为自己创建的枚举类
    Map<EnumTest,String> em = Collections.synchronizedMap(new EnumMap<EnumTest,String>(EnumTest.class) );

     

     特性:

    1.线程不安全

    2.和HashMap相比,效率要比HashMap高

     

    展开全文
  • 剖析EnumMap

    2018-09-21 08:56:26
    如果需要一个Map的实现类,并且键的类型为枚举类型,可以使用HashMap,但应该使用一个专门的实现类EnumMap。 为什么要有一个专门的类呢?我们之前介绍过枚举的本质,主要是因为枚举类型有两个特征,一是它可能的值...

    如果需要一个Map的实现类,并且键的类型为枚举类型,可以使用HashMap,但应该使用一个专门的实现类EnumMap。

    为什么要有一个专门的类呢?我们之前介绍过枚举的本质,主要是因为枚举类型有两个特征,一是它可能的值是有限的且预先定义的,二是枚举值都有一个顺序,这两个特征使得可以更为高效的实现Map接口。

    我们先来看EnumMap的用法,然后看它到底是怎么实现的。

    用法

    举个简单的例子,比如,有一批关于衣服的记录,我们希望按尺寸统计衣服的数量。

    定义一个简单的枚举类,Size,表示衣服的尺寸:

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    定义一个简单类,Clothes,表示衣服:

    class Clothes {
        String id;
        Size size;
        
        public Clothes(String id, Size size) {
            this.id = id;
            this.size = size;
        }
    
        public String getId() {
            return id;
        }
    
        public Size getSize() {
            return size;
        }
    }

    有一个表示衣服记录的列表List<Clothes>,我们希望按尺寸统计数量,统计方法可以为:

    public static Map<Size, Integer> countBySize(List<Clothes> clothes){
        Map<Size, Integer> map = new EnumMap<>(Size.class);
        for(Clothes c : clothes){
            Size size = c.getSize();
            Integer count = map.get(size);
            if(count!=null){
                map.put(size, count+1);
            }else{
                map.put(size, 1);
            }
        }
        return map;
    }

    大部分代码都很简单,需要注意的是EnumMap的构造方法,如下所示:

    Map<Size, Integer> map = new EnumMap<>(Size.class);

    与HashMap不同,它需要传递一个类型信息,我们在37节简单介绍过运行时类型信息,Size.class表示枚举类Size的运行时类型信息,Size.class也是一个对象,它的类型是Class。

    为什么需要这个参数呢?没有这个,EnumMap就不知道具体的枚举类是什么,也无法初始化内部的数据结构。

    使用以上的统计方法也是很简单的,比如:

    List<Clothes> clothes = Arrays.asList(new Clothes[]{
            new Clothes("C001",Size.SMALL),
            new Clothes("C002", Size.LARGE),
            new Clothes("C003", Size.LARGE),
            new Clothes("C004", Size.MEDIUM),
            new Clothes("C005", Size.SMALL),
            new Clothes("C006", Size.SMALL),
    });
    System.out.println(countBySize(clothes));

    输出为:

    {SMALL=3, MEDIUM=1, LARGE=2}

    需要说明的是,EnumMap是保证顺序的,输出是按照键在枚举中的顺序的。

    除了以上介绍的构造方法,EnumMap还有两个构造方法,可以接受一个键值匹配的EnumMap或普通Map,如下所示:

    public EnumMap(EnumMap<K, ? extends V> m)
    public EnumMap(Map<K, ? extends V> m)

    比如:

    Map<Size,Integer> hashMap = new HashMap<>();
    hashMap.put(Size.LARGE, 2);
    hashMap.put(Size.SMALL, 1);
    Map<Size, Integer> enumMap = new EnumMap<>(hashMap);

    以上就是EnumMap的基本用法,与HashMap的主要不同,一是构造方法需要传递类型参数,二是保证顺序。

    有人可能认为,对于枚举,使用Map是没有必要的,比如对于上面的统计例子,可以使用一个简单的数组:

    public static int[] countBySize(List<Clothes> clothes){
        int[] stat = new int[Size.values().length];
        for(Clothes c : clothes){
            Size size = c.getSize();
            stat[size.ordinal()]++;
        }
        return stat;
    }

    这个方法可以这么使用:

    List<Clothes> clothes = Arrays.asList(new Clothes[]{
            new Clothes("C001",Size.SMALL),
            new Clothes("C002", Size.LARGE),
            new Clothes("C003", Size.LARGE),
            new Clothes("C004", Size.MEDIUM),
            new Clothes("C005", Size.SMALL),
            new Clothes("C006", Size.SMALL),
    });
    int[] stat = countBySize(clothes);
    for(int i=0; i<stat.length; i++){
        System.out.println(Size.values()[i]+": "+ stat[i]);
    }

    输出为:

    SMALL 3
    MEDIUM 1
    LARGE 2

    可以达到同样的目的。但,直接使用数组需要自己维护数组索引和枚举值之间的关系,正如枚举的优点是简洁、安全、方便一样,EnumMap同样是更为简洁、安全、方便,它内部也是基于数组实现的,但隐藏了细节,提供了更为方便安全的接口。

    实现原理

    下面我们来看下具体的代码,从内部组成开始。

    内部组成

    EnumMap有如下实例变量:

    private final Class<K> keyType;
    private transient K[] keyUniverse;
    private transient Object[] vals;
    private transient int size = 0;

    keyType表示类型信息,keyUniverse表示键,是所有可能的枚举值,vals表示键对应的值,size表示键值对个数。

    构造方法

    EnumMap的基本构造方法代码为:

    public EnumMap(Class<K> keyType) {
        this.keyType = keyType;
        keyUniverse = getKeyUniverse(keyType);
        vals = new Object[keyUniverse.length];
    }

    调用了getKeyUniverse以初始化键数组,其代码为:

    private static <K extends Enum<K>> K[] getKeyUniverse(Class<K> keyType) {
        return SharedSecrets.getJavaLangAccess()
                                        .getEnumConstantsShared(keyType);
    }

    这段代码又调用了其他一些比较底层的代码,就不列举了,原理是最终调用了枚举类型的values方法,values方法返回所有可能的枚举值。关于values方法,我们在枚举的本质一节介绍过其用法和实现原理,这里就不赘述了。

    保存键值对

    put方法的代码为:

    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);
    }

    首先调用typeCheck检查键的类型,其代码为:

    private void typeCheck(K key) {
        Class keyClass = key.getClass();
        if (keyClass != keyType && keyClass.getSuperclass() != keyType)
            throw new ClassCastException(keyClass + " != " + keyType);
    }

    如果类型不对,会抛出异常。类型正确的话,调用ordinal获取索引index,并将值value放入值数组vals[index]中。EnumMap允许值为null,为了区别null值与没有值,EnumMap将null值包装成了一个特殊的对象,有两个辅助方法用于null的打包和解包,打包方法为maskNull,解包方法为unmaskNull。这个特殊对象及两个方法的代码为:

    private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }
    
        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };
    
    private Object maskNull(Object value) {
        return (value == null ? NULL : value);
    }
    
    private V unmaskNull(Object value) {
        return (V) (value == NULL ? null : value);
    }

    根据键获取值

    get方法的代码为:

    public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum)key).ordinal()]) : null);
    }

    键有效的话,通过ordinal方法取索引,然后直接在值数组vals里找。isValidKey的代码与typeCheck类似,但是返回boolean值而不是抛出异常,代码为:

    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;
    }

    查看是否包含某个值

    containsValue方法的代码为:

    public boolean containsValue(Object value) {
        value = maskNull(value);
    
        for (Object val : vals)
            if (value.equals(val))
                return true;
    
        return false;
    }

    展开全文
  • 文章目录简介EnumMap什么时候使用EnumMapEnumSet总结 一文弄懂EnumMap和EnumSet 简介 一般来说我们会选择使用HashMap来存储key-value格式的数据,考虑这样的特殊情况,一个HashMap的key都来自于一个Enum类,这样的...
  • EnumMap示例

    2014-11-04 14:54:52
    import java.util.EnumMap;/** * enumMap示例 * * @author lzh * */public class EnColor {public enum Color {RED, BLUE, GREEN, BLACK, WHITE;}public EnumMap color = new EnumMap(Color.class);public EnColor()...
    import java.util.EnumMap;
    
    /**
     * enumMap示例
     * 
     * @author lzh
     * 
     */
    public class EnColor {
    
    	public enum Color {
    		RED, BLUE, GREEN, BLACK, WHITE;
    	}
    
    	public EnumMap<Color, Object> color = new EnumMap<Color, Object>(
    			Color.class);
    
    	public EnColor() {
    		color.put(Color.RED, "红色");
    		color.put(Color.BLUE, "蓝色");
    		color.put(Color.GREEN, "绿色");
    		color.put(Color.BLACK, "黑色");
    		color.put(Color.WHITE, "白色");
    	}
    
    	public static void main(String[] args) {
    		EnColor ec = new EnColor();
    		System.out.println(ec.color.get(EnColor.Color.RED));
    		System.out.println(ec.color.get(EnColor.Color.BLACK));
    	}
    }
    console:
    红色
    黑色
    
    展开全文
  • EnumMap源码解析

    2019-08-03 11:57:43
    如果需要一个Map的实现类,并且键的类型为枚举类型,可以使用HashMap,但应该使用一个专门的实现类EnumMap 因为枚举类型有两个特征,一是它可能的值是有限的且预先定义的,二是枚举值都有一个顺序,这两个特征使得...
  • java enummap EnumMap类containsKey()方法 (EnumMap Class containsKey() method) containsKey() method is available in java.util package. containsKey()方法在java.util包中可用。 containsKey() method is used...
  • java enummap EnumMap类equals()方法 (EnumMap Class equals() method) equals() method is available in java.util package. equals()方法在java.util包中可用。 equals() method is used to check whether this ...
  • java enummap EnumMap类remove()方法 (EnumMap Class remove() method) remove() method is available in java.util package. remove()方法在java.util包中可用。 remove() method is used to clear all mappings ...
  • java enummap EnumMap类entrySet()方法 (EnumMap Class entrySet() method) entrySet() method is available in java.util package. entrySet()方法在java.util包中可用。 entrySet() method is used to get a set ...
  • java enummap EnumMap类的get()方法 (EnumMap Class get() method) get() method is available in java.util package. get()方法在java.util包中可用。 get() method is used to get the value mapped with the ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,428
精华内容 2,971
关键字:

enummap