EnumMap & EnumSet
一、EnumMap
一)基本用法
public static Map<Size, Integer> countBySize(List<Clothes> clothes) { EnumMap<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; } public static void main(String[] args) { 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的实例变量:
private final Class<K> keyType; //类型信息 private transient K[] keyUniverse; //表示键,是所有可能的枚举值 private transient Object[] vals; //表示键对应的值 private transient int size = 0; //表示键值对个数
基本构造方法:
public EnumMap(Class<K> keyType) { this.keyType = keyType; keyUniverse = getKeyUniverse(keyType); //最终调用了枚举类型的values方法,该方法返回所有可能的枚举值 vals = new Object[keyUniverse.length]; }
保存键值对的put方法:
public V put(K key, V value) { //检查键的类型 typeCheck(key); //获取索引 int index = key.ordinal(); //放入值value Object oldValue = vals[index]; //如果有值为null打包null值,这是为了区别null值与没有值 vals[index] = maskNull(value); if(oldValue == null) size++; return unmaskNull(oldValue); }
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); }
二、EnumSet
EnumSet的实现与EnumMap没有任何关系,而是用极为精简高效的位向量实现的。
位向量是计算机程序中解决问题的一种常用方式,我们有必要理解和掌握。
一)基本用法
通过EnumSet的静态工厂方法,可以创建EnumSet对象,比如:
//创建一个指定类型的EnumSet,不含任何元素,创建的EnumSet实际类型是EnumSet的子类 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
用法举例:
public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
EnumSet<Day> days = EnumSet.noneOf(Day.class); days.add(Day.FRIDAY); days.add(Day.SUNDAY); days.add(Day.MONDAY); System.out.println(days); //[MONDAY, FRIDAY, SUNDAY]
EnumSet的其他静态工厂方法:
//初始集合包括指定枚举类型的所有枚举值 <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) //初始集合包括枚举值中指定范围的元素 <E extends Enum<E>> EnumSet<E> range(E from, E to) //包含指定集合的补集 <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s) //包含参数中的所有元素 <E extends Enum<E>> EnumSet<E> of(E e) <E extends Enum<E>> EnumSet<E> of(E e1, E e2) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5) <E extends Enum<E>> EnumSet<E> of(E first, E... rest) //包含参数中的所有元素 <E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s) <E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
二、应用场景
Worker[] workers = new Worker[]{ new Worker("Tom", EnumSet.of(Day.FRIDAY, Day.MONDAY, Day.SATURDAY)), new Worker("Jerry", EnumSet.of(Day.MONDAY, Day.WEDNESDAY, Day.THURSDAY)), new Worker("Teddy", EnumSet.of(Day.TUESDAY, Day.FRIDAY, Day.WEDNESDAY)) }; //哪些天一个人都不会来 EnumSet<Day> allDays = EnumSet.allOf(Day.class); for (Worker w : workers) { allDays.remove(w.getWorkingDays()); }
三、实现原理
位向量:用一个位表示一个元素状态,用一组位表示集合状态,
每个位对应一个元素,而状态只可能有两种。
比如之前的枚举类Day,它有7个枚举值,一个Day的集合就可以用一个字节byte表示,
最高位不用,设为0,从右到左,每位对应一个枚举值,1表示包含该元素,0表示不包含该元素。
EnumSet的实现,首先是主要实例变量:
final Class<E> elementType; //表示类型信息 final Enum[] universe; //表示所有枚举值
EnumSet自身没有记录元素个数的变量,也没有位向量,它们是子类维护的。
对于RegularEnumSet,它用一个long类型表示位向量,代码为:
private long elements = 0L;
RegularEnumSet没有定义元素个数的变量,都是实时计算出来的:
public int size() { return Long.bitCount(elements); }
而对于JumboEnumSet,用一个long数组表示,有单独的size变量
private long elements[]; private int size = 0;
EnumSet的静态工厂方法:
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
RegularEnum 和 SetJumboEnumSet的构造方法:
RegularEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); } JumboEnumSet(Class<E>elementType, Enum[] universe) { super(elementType, universe); elements = new long[(universe.length + 63) >>> 6]; }
EnumSet(Class<E>elementType, Enum[] universe) { this.elementType = elementType; this.universe = universe; }
其他源码略。
四、实现原理
对于只有两种状态,且需要进行集合运算的数据,使用位向量表示
、位运算进行处理,是计算机程序中一种常见的思维方式。