1. EnumMap
的基本概念
- 如果需要一个
Map
的实现类,并且键的类型为枚举类型,可以使用HashMap
,但应该使用一个专门的实现类EnumMap
EnumMap
内部基于两个数组实现
2. 针对 Map
的类型为枚举类型,为什么要有一个专门的 Map
的实现类
主要是因为枚举类型有两个主要特征
- 枚举可能的值是有限的且预先定义的
- 枚举值都有一个顺序
这两个特征使得可以更为高效地实现
Map
接口
3. 有一批关于衣服的纪录,我们希望按尺寸统计衣服的数量,请用数组和 EnumMap 分别实现,并分析这两种实现的区别(表示衣服尺寸的枚举类和表示衣服的实体类以及衣服列表分别如下)
1 | //表示衣服尺寸的枚举类 |
代码
数组实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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;
}
//使用 int[] countBySize() 方法
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: 2EnumMap
实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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, count);
}
}
return map;
}
//使用 Map<Size, Integer> countBySize() 方法
System.out.println(countBySize(clothes));
//输出
{SMALL=3, MEDIUM=1, LARGE=2}
数组实现中:直接使用数组需要自己维护数组索引和枚举值之间的关系
EnumMap
实现中:EnumMap
的构造方法与HashMap
不同,它需要传递一个类型信息Size.class
表示枚举类Size
的运行时类型信息,Size.class
也是一个对象,它的类型是Class
- 之所以需要这个参数,是因为如果没有的话,
EnumMap
就不知道具体的枚举类型是什么,也无法初始化内部的数据结构 - 与
HashMap
不同,EnumMap
是保证顺序的,输出是按照键在枚举中的顺序
区别:正如枚举的优点是简洁、安全、方便一样,
EnumMap
同样是更为简洁、安全、方便,它内部也是基于数组实现的,但隐藏了细节,提供了更为安全的接口
4. EnumMap
的内部组成
EnumMap
有如下实例变量1
2
3
4private final Class<K> keyType; //表示类型信息
private transient K[] keyUniverse; //表示键,是所有可能的枚举值
private transient Object[] vals; //表示键对应的值
private transient int size = 0; //表示键值对个数EnumMap
的构造方法代码1
2
3
4
5public EnumMap(Class<K> keyType) {
this.keyType = keyType;
keyUniverse = getKeyUniverse(keyType);
vals = new Object[keyUniverse.length];
}- 调用了
getKeyUniverse()
以初始化键数组,这段代码又调用了其他一些比较底层的代码,就不列举了 - 原理是最终调用了枚举类型的
values()
方法,values()
方法返回所有可能的枚举值
- 调用了
EnumMap
允许值为null
,为了区别null
值与没有值,EnumMap
将null
值包装成了一个特殊的对象,有两个辅助方法用于null
的打包和解包null
值的包装对象1
2
3
4
5
6
7
8private static final Object NULL = new Object() {
public int hashCode() {
return 0;
}
public String toString() {
return "java.util.EnumMap.NULL";
}
}打包方法
maskNull()
1
2
3private Object maskNull(Object value) {
return (value == null ? NULL : value);
}解包方法
unmaskNull()
1
2
3private V unmaskNull(Object value) {
return (V) (value == NULL ? null : value);
}
5. EnumMap
的特点
基本用法
- 如果需要一个
Map
且键是枚举类型,则应该用它 - 简洁、方便、安全
- 如果需要一个
实现原理
- 内部有两个数组,长度相同。一个表示所有可能的键;一个表示对应的值,值为
null
表示没有该键值对 - 键都有一个对应的索引,根据索引可直接访问和操作其键和值,效率很高
- 内部有两个数组,长度相同。一个表示所有可能的键;一个表示对应的值,值为