1 簡介 我們知道 只是一個介面,它有多種實現,Java中最常用的是 了。而本文想講述的是另一個實現: 。它是枚舉類型的 ,要求它的Key值都必須是枚舉型的。 2 創建你的EnumMap 既然是關於枚舉類型的Map,我們先創建一個枚舉,以便後續使用: 2.1 創建EnumMap的三種方法 JDK提供 ...
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
,性能會更好哦。
歡迎關註公眾號<南瓜慢說>,將持續為你更新...
多讀書,多分享;多寫作,多整理。