1 简介
我们知道 Map
只是一个接口,它有多种实现,Java 中最常用的是 HashMap
了。而本文想讲述的是另一个实现:EnumMap
。它是枚举类型的Map
,要求它的 Key 值都必须是枚举型的。
2 创建你的 EnumMap
既然是关于枚举类型的 Map,我们先创建一个枚举,以便后续使用:
public enum Directions {NORTH, SOUTH, EAST, WEST}
2.1 创建 EnumMap 的三种方法
JDK 提供的创建 EnumMap
的方法有三种,代码如下:
//new EnumMap
EnumMap<Direction, String> enumMap = new EnumMap<>(Direction.class);
enumMap.put(Direction.EAST, "东");
enumMap.put(Direction.SOUTH, "南");
// 从 EnumMap 复制
EnumMap<Direction, String> enumMapCopyEnumMap = new EnumMap<>(enumMap);
assertEquals(enumMap, enumMapCopyEnumMap);
// 从 Map 复制
Map<Direction, String> hashMap = Maps.newHashMap();
hashMap.put(Direction.EAST, "东");
hashMap.put(Direction.SOUTH, "南");
EnumMap<Direction, String> enumMapCopyHashMap = new EnumMap<>(hashMap);
assertEquals(enumMap, enumMapCopyHashMap);
- (1) 使用
new EnumMap()
方法时,与HashMap
不同,它必须传入一个枚举的类型才能创建对象; - (2) 从
EnumMap
复制,这时传入的参数为EnumMap
; - (3) 从
Map
复制,传入的参数为Map
,但要求 Key 的类型必须是枚举型。
2.2 聪明的 Guava
其实可以综合上面三种情况,实际就是两种方法:
- (1) 使用
new EnumMap(Class<K> keyType)
- (2) 使用
new EnumMap(Map<K, ? extends V> m)
聪明的 Guava
就只提供了这两种方法,如下:
// 使用 Guava 创建
EnumMap<Direction, String> enumMapGuava = Maps.newEnumMap(Direction.class);
enumMapGuava.put(Direction.SOUTH, "南");
assertEquals(1, enumMapGuava.size());
enumMapGuava = Maps.newEnumMap(enumMap);
assertEquals(enumMap, enumMapGuava);
3 基本操作
提供的方法与 Map 当然是一样的,操作十分方便,代码如下:
@Test
public void operations() {EnumMap<Direction, String> map = Maps.newEnumMap(Direction.class);
// 增加
map.put(Direction.EAST, "东");
map.put(Direction.SOUTH, "南");
map.put(Direction.WEST, "西");
// 查询
assertTrue(map.containsKey(Direction.EAST));
assertFalse(map.containsKey(Direction.NORTH));
// 删除
map.remove(Direction.EAST);
assertFalse(map.containsKey(Direction.EAST));
assertFalse(map.remove(Direction.WEST, "北"));
assertTrue(map.remove(Direction.WEST, "西"));
// 清空
map.clear();
assertEquals(0, map.size());
}
需要特别指出的是删除方法,可以传入 Key 和 Value 两个参数,map.remove(Direction.WEST, "西")
当键值对匹配时,则可以删除成功;map.remove(Direction.WEST, "北")
匹配失败,则不会删除。
4 集合视图
4.1 有序性
与 Map 接口提供的功能一样,EnumMap
也能返回它的所有 Values、Keys 和 Entry 等。但与 HashMap
不同的是,EnumMap
返回的视图是有序的,这个顺序不是插入的顺序,而是枚举定义的顺序。代码如下:
EnumMap<Direction, String> map = Maps.newEnumMap(Direction.class);
map.put(Direction.EAST, "东");
map.put(Direction.SOUTH, "南");
map.put(Direction.WEST, "西");
map.put(Direction.NORTH, "北");
// 返回所有 Value
Collection<String> values = map.values();
values.forEach(System.out::println);
// 返回所有 Key
Set<Direction> keySet = map.keySet();
keySet.forEach(System.out::println);
// 返回所有 <Key,Value>
Set<Map.Entry<Direction, String>> entrySet = map.entrySet();
entrySet.forEach(entry -> {System.out.println(entry.getKey() + ":" + entry.getValue());
});
输出的结果如下:
北
南
东
西
NORTH
SOUTH
EAST
WEST
NORTH: 北
SOUTH: 南
EAST: 东
WEST: 西
这个顺序与我们定义枚举的顺序确实是一样的,而与添加的顺序无关。
4.2 联动性
除了有序性之外,EnumMap
返回的集合视图还有一点不同就是联动性,即牵一发而动全身。改变其中一个,另外的也跟着变了。看代码一下就明白了:
//Values、keySet、entrySet 改变会影响其它
values.remove("东");
assertEquals(3, map.size());
assertEquals(3, keySet.size());
assertEquals(3, entrySet.size());
keySet.remove(Direction.WEST);
assertEquals(2, map.size());
assertEquals(2, values.size());
assertEquals(2, entrySet.size());
entrySet.removeIf(entry -> Objects.equals(entry.getValue(), "北"));
assertEquals(1, map.size());
assertEquals(1, keySet.size());
assertEquals(1, values.size());
//Map 的改变会影响其它视图
map.clear();
assertEquals(0, values.size());
assertEquals(0, keySet.size());
assertEquals(0, entrySet.size());
5 性能
性能是我们选择 EnumMap
的主要原因之一,那为何它性能会比优秀的 HashMap
还要好呢?通过看源码可以得知:
(1)底层是通过两个数组来存放数据的,一个放 Keys,一个放 Values;
(2)因为 Key 值是枚举类型,即一开始就确定了元素个数,所以在创建一个 EnumMap
的时候,存放数据的数组就已经确定了大小,不用考虑后续扩容带来的性能问题。
(3)枚举本身就是固定顺序的,可以通过 Enum.ordinal()
方法获得顺序,这个便可以作为查询与插入的索引,而不用计算 HashCode
,性能也会比较快。这个顺序也就是数组下标。这也是EnumMap
的集合视图都是有序的原因。
(4)因为大小固定,则不用考虑加载因子,也不会有哈希冲突的问题,空间复杂度小。
6 结论
本文介绍了 EnumMap
作为一个 Map
的特殊实现的创建、使用、集合视图和性能分析,发现它的确是有过人之处的。当我们的 Key 值是枚举时,不妨可以试一试EnumMap
,性能会更好哦。
欢迎关注公众号 <南瓜慢说>,将持续为你更新 …
多读书,多分享;多写作,多整理。