每個有經驗的Java程序員都在某處實現過Map<K, List<V>>或Map<K, Set<V>>,並且要忍受這個結構的笨拙。
假如目前有個需求是給兩個年級添加5個學生,並且統計出一年級學生的信息:
public class MultimapTest { class Student { String name; int age; } private static final String CLASS_NAME_1 = "一年級"; private static final String CLASS_NAME_2 = "二年級"; Map<String, List<Student>> StudentsMap = new HashMap<String, List<Student>>(); public void testStudent() { for (int i = 0; i < 5; i++) { Student student = new Student(); student.name = "Tom" + i; student.age = 6; addStudent(CLASS_NAME_1, student); } for (int i = 0; i < 5; i++) { Student student = new Student(); student.name = "Jary" + i; student.age = 7; addStudent(CLASS_NAME_2, student); } List<Student> class1StudentList = StudentsMap.get(CLASS_NAME_1); for (Student stu : class1StudentList) { System.out.println("一年級學生 name:" + stu.name + " age:" + stu.age); } } public void addStudent(String className, Student student) { List<Student> students = StudentsMap.get(className); if (students == null) { students = new ArrayList<Student>(); StudentsMap.put(className, students); } students.add(student); } public static void main(String[] args) { MultimapTest multimapTest = new MultimapTest(); multimapTest.testStudent(); } }
可以看到我們實現起來特別麻煩,需要檢查key是否存在,不存在時則創建一個,存在時在List后面添加上一個。這個過程是比較痛苦的,如果希望檢查List中的對象是否存在,刪除一個對象,或者遍歷整個數據結構,那么則需要更多的代碼來實現。
Multimap 提供了一個方便地把一個鍵對應到多個值的數據結構。
我們可以這樣理解Multimap:”鍵-單個值映射”的集合(例如:a -> 1 a -> 2 a ->4 b -> 3 c -> 5)
特點:不會有任何鍵映射到空集合:一個鍵要么至少到一個值,要么根本就不在Multimap中。
主要方法介紹:
- put(K, V):添加鍵到單個值的映射
- putAll(K, Iterable<V>):依次添加鍵到多個值的映射
- remove(K, V):移除鍵到值的映射;如果有這樣的鍵值並成功移除,返回true
- removeAll(K):清除鍵對應的所有值,返回的集合包含所有之前映射到K的值,但修改這個集合就不會影響Multimap了
- replaceValues(K, Iterable<V>):清除鍵對應的所有值,並重新把key關聯到Iterable中的每個元素。返回的集合包含所有之前映射到K的值
Multimap的視圖
Multimap還支持若干強大的視圖:
- asMap為Multimap<K, V>提供Map<K,Collection<V>>形式的視圖。返回的Map支持remove操作,並且會反映到底層的 Multimap,但它不支持put或putAll操作。更重要的是,如果你想為Multimap中沒有的鍵返回null,而不是一個新的、可寫的空集 合,你就可以使用asMap().get(key)。(你可以並且應當把asMap.get(key)返回的結果轉化為適當的集合類型——如 SetMultimap.asMap.get(key)的結果轉為Set,ListMultimap.asMap.get(key)的結果轉為List ——Java類型系統不允許ListMultimap直接為asMap.get(key)返回List——譯者注:也可以用Multimaps中的asMap靜態方法幫你完成類型轉換)
- entries用Collection<Map.Entry<K, V>>返回Multimap中所有”鍵-單個值映射”——包括重復鍵。(對SetMultimap,返回的是Set)
- keySet用Set表示Multimap中所有不同的鍵。
- keys用Multiset表示Multimap中的所有鍵,每個鍵重復出現的次數等於它映射的值的個數。可以從這個Multiset中移除元素,但不能做添加操作;移除操作會反映到底層的Multimap。
- values()用 一個”扁平”的Collection<V>包含Multimap中的所有值。這有一點類似於 Iterables.concat(multimap.asMap().values()),但它直接返回了單個Collection,而不像 multimap.asMap().values()那樣是按鍵區分開的Collection。
Multimap不是Map
Multimap<K, V>不是Map<K,Collection<V>>,雖然某些Multimap實現中可能使用了map。它們之間的顯著區別包括:
- Multimap.get(key)總是返回非null、但是可能空的集合。這並不意味着Multimap為相應的鍵花費內存創建了集合,而只是提供一個集合視圖方便你為鍵增加映射值——譯者注:如果有這樣的鍵,返回的集合只是包裝了Multimap中已有的集合;如果沒有這樣的鍵,返回的空集合也只是持有Multimap引用的棧對象,讓你可以用來操作底層的Multimap。因此,返回的集合不會占據太多內存,數據實際上還是存放在Multimap中。
- 如果你更喜歡像Map那樣,為Multimap中沒有的鍵返回null,請使用asMap()視圖獲取一個Map<K, Collection<V>>。(或者用靜態方法Multimaps.asMap()為ListMultimap返回一個Map<K, List<V>>。對於SetMultimap和SortedSetMultimap,也有類似的靜態方法存在)
- 當且僅當有值映射到鍵時,Multimap.containsKey(key)才會返回true。尤其需要注意的是,如果鍵k之前映射過一個或多個值,但它們都被移除后,Multimap.containsKey(key)會返回false。
- Multimap.entries()返回Multimap中所有”鍵-單個值映射”——包括重復鍵。如果你想要得到所有”鍵-值集合映射”,請使用asMap().entrySet()。
- Multimap.size()返回所有”鍵-單個值映射”的個數,而非不同鍵的個數。要得到不同鍵的個數,請改用Multimap.keySet().size()。
測試類:
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; public class MultimapTest { class Student { String name; int age; } private static final String CLASS_NAME_1 = "一年級"; private static final String CLASS_NAME_2 = "二年級"; Multimap<String, Student> multimap = ArrayListMultimap.create(); public void testStudent() { for (int i = 0; i < 5; i++) { Student student = new Student(); student.name = "Tom" + i; student.age = 6; multimap.put(CLASS_NAME_1, student); } for (int i = 0; i < 5; i++) { Student student = new Student(); student.name = "Jary" + i; student.age = 7; multimap.put(CLASS_NAME_2, student); } for (Student stu : multimap.get(CLASS_NAME_1)) { System.out.println("一年級學生 name:" + stu.name + " age:" + stu.age); } //判斷鍵是否存在 if(multimap.containsKey(CLASS_NAME_1)){ System.out.println("鍵值包含:"+CLASS_NAME_1); } //”鍵-單個值映射”的個數 System.out.println(multimap.size()); //不同鍵的個數 System.out.print(multimap.keySet().size()); } public static void main(String[] args) { MultimapTest multimapTest = new MultimapTest(); multimapTest.testStudent(); } }
