Map是將鍵映射到值的對象,map不能包含重複的鍵:每一個鍵最多能夠映射一個值,它模擬數學函數抽象。Map
接口包括基本操做的方法(如put
、get
、remove
、containsKey
、containsValue
、size
和empty
),批量操做(如putAll
和clear
)和集合視圖(如keySet
、entrySet
和values
)。html
Java平臺包含三個通用Map
實現:HashMap、TreeMap和LinkedHashMap,它們的行爲和性能徹底相似於HashSet
、TreeSet
和LinkedHashSet
,如Set接口部分所述。java
本頁的其他部分詳細討論了Map
接口,但首先,這裏有一些使用JDK 8聚合操做收集到Map
的示例,對現實世界對象進行建模是面向對象編程中的常見任務,所以能夠合理地認爲某些程序可能會按部門對員工進行分組:react
// Group employees by department Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment));
或者按部門計算全部工資的總和:git
// Compute sum of salaries by department Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.summingInt(Employee::getSalary)));
或者經過成績及格或成績不及格分組學生:github
// Partition students into passing and failing Map<Boolean, List<Student>> passingFailing = students.stream() .collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD));
你還能夠按城市分組:編程
// Classify Person objects by city Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
或者甚至級聯兩個收集器按州和城市對人進行分類:segmentfault
// Cascade Collectors Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)))
一樣,這些只是如何使用新JDK 8 API的幾個示例,有關lambda表達式和聚合操做的深刻介紹,請參閱標題爲聚合操做的課程。api
Map
(put
、get
、containsKey
、containsValue
、size
和isEmpty
)的基本操做與Hashtable
中的對應操做徹底相同,如下程序生成其參數列表中找到的單詞的頻率表,頻率表將每一個單詞映射到它在參數列表中出現的次數。安全
import java.util.*; public class Freq { public static void main(String[] args) { Map<String, Integer> m = new HashMap<String, Integer>(); // Initialize frequency table from command line for (String a : args) { Integer freq = m.get(a); m.put(a, (freq == null) ? 1 : freq + 1); } System.out.println(m.size() + " distinct words:"); System.out.println(m); } }
關於這個程序惟一棘手的問題是put
語句的第二個參數,該參數是一個條件表達式,若是單詞以前從未出現過,則其頻率設置爲1,若是單詞已經出現,則其頻率設置爲當前值加1,嘗試使用如下命令運行此程序:oracle
java Freq if it is to be it is up to me to delegate
該程序產生如下輸出。
8 distinct words: {to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}
假設你但願按字母順序查看頻率表,你所要作的就是將Map
的實現類型從HashMap
更改成TreeMap
,進行這種更改會致使程序從同一命令行生成如下輸出。
8 distinct words: {be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
相似地,你能夠經過將map
的實現類型更改成LinkedHashMap
,使程序按照單詞首次出如今命令行上的順序打印頻率表,這樣作會產生如下輸出。
8 distinct words: {if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}
這種靈活性提供了基於接口的框架功能的有力說明。
與Set和List接口同樣,Map
強化了對equals
和hashCode
方法的要求,所以能夠比較兩個Map
對象的邏輯相等性,而不考慮它們的實現類型,若是兩個Map
實例表示相同的鍵值映射,則它們是相等的。
按照慣例,全部通用Map
實現都提供構造函數,這些構造函數接受Map
對象並初始化新Map
以包含指定Map
中的全部鍵值映射。這個標準的Map
轉換構造函數徹底相似於標準的Collection
構造函數:它容許調用者建立一個所需實現類型的Map
,該Map
最初包含另外一個Map
中的全部映射,而無論其餘Map
的實現類型如何。例如,假設你有一個名爲m
的Map
,如下單行建立一個新的HashMap
,最初包含與m
相同的全部鍵值映射。
Map<K, V> copy = new HashMap<K, V>(m);
clear
的操做徹底符合你的想法:它從Map
中刪除全部映射。putAll
操做是Collection
接口的addAll
操做的Map
模擬,除了明顯使用將一個Map轉儲到另外一個Map以外,它還有第二個更微妙的用途,假設Map
用於表示屬性—值對的集合,putAll
操做與Map
轉換構造函數結合使用,提供了一種使用默認值實現屬性映射建立的簡潔方法。如下是演示此技術的靜態工廠方法。
static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) { Map<K, V> result = new HashMap<K, V>(defaults); result.putAll(overrides); return result; }
Collection
視圖方法容許以這三種方式將Map
視爲Collection
:
keySet
— Map
中包含鍵的Set
。values
— Map
中包含值的Collection
,此Collection
不是Set
,由於多個鍵能夠映射到相同的值。entrySet
— Map
中包含的鍵值對的Set
,Map
接口提供了一個名爲Map.Entry
的小型嵌套接口,該接口是此Set
中元素的類型。Collection
視圖提供迭代Map
的惟一方法,此示例說明了使用for-each
構造迭代Map
中的鍵的標準語法:
for (KeyType key : m.keySet()) System.out.println(key);
使用迭代器:
// Filter a map based on some // property of its keys. for (Iterator<Type> it = m.keySet().iterator(); it.hasNext(); ) if (it.next().isBogus()) it.remove();
迭代值的語法是相似的,如下是迭代鍵值對的語法。
for (Map.Entry<KeyType, ValType> e : m.entrySet()) System.out.println(e.getKey() + ": " + e.getValue());
起初,許多人擔憂這些語法可能會很慢,由於每次調用Collection
視圖操做時Map
都必須建立一個新的Collection
實例,放鬆:每次要求給定的Collection
視圖時,Map
都沒有理由不能老是返回相同的對象,這正是java.util
中全部Map
實現的功能。
對於全部這三個Collection
視圖,調用Iterator
的remove
操做將從支持Map
中刪除相關條目,假設支持Map
一開始就支持元素刪除,這由前面的過濾語法說明。
使用entrySet
視圖,還能夠經過在迭代期間調用Map.Entry
的setValue
方法來更改與鍵關聯的值(一樣,假設Map
一開始就支持值修改)。請注意,這些是在迭代期間修改Map
的惟一安全方法,若是在迭代進行過程當中以任何其餘方式修改基礎Map
,則行爲是未指定的。
Collection
視圖支持以多種形式刪除元素 — remove
、removeAll
、retainAll
和clear
操做,以及Iterator.remove
操做(一樣,這假設支持Map
支持元素刪除)。
Collection
視圖在任何狀況下都不支持元素添加,對於keySet
和values
視圖沒有任何意義,而且對於entrySet
視圖沒有必要,由於支持Map
的put
和putAll
方法提供相同的功能。
應用於Collection
視圖時,批量操做(containsAll
、removeAll
和retainAll
)是使人驚訝的強大工具。對於初學者,假設你想知道一個Map
是不是另外一個Map
的子圖 — 也就是說,第一個Map
是否包含第二個Map
中的全部鍵值映射,如下語法能夠解決這個問題。
if (m1.entrySet().containsAll(m2.entrySet())) { ... }
沿着相似的路線,假設你想知道兩個Map
對象是否包含全部相同鍵的映射。
if (m1.keySet().equals(m2.keySet())) { ... }
假設你有一個表示屬性—值對集合的Map
,以及兩個表示所需屬性和容許屬性的Set
(容許的屬性包括必需的屬性),如下代碼段肯定屬性映射是否符合這些約束,若是不符合則打印詳細的錯誤消息。
static <K, V> boolean validate(Map<K, V> attrMap, Set<K> requiredAttrs, Set<K>permittedAttrs) { boolean valid = true; Set<K> attrs = attrMap.keySet(); if (! attrs.containsAll(requiredAttrs)) { Set<K> missing = new HashSet<K>(requiredAttrs); missing.removeAll(attrs); System.out.println("Missing attributes: " + missing); valid = false; } if (! permittedAttrs.containsAll(attrs)) { Set<K> illegal = new HashSet<K>(attrs); illegal.removeAll(permittedAttrs); System.out.println("Illegal attributes: " + illegal); valid = false; } return valid; }
假設你想知道兩個Map
對象共有的全部鍵。
Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet()); commonKeys.retainAll(m2.keySet());
相似的語法能夠爲你提供共同的值。
到目前爲止提出的全部語法都是非破壞性的,也就是說,它們不會修改支持Map
,這裏有一些,假設你要刪除一個Map
與另外一個Map
共有的全部鍵值對。
m1.entrySet().removeAll(m2.entrySet());
假設你要從一個Map
中刪除在另外一個Map
中具備映射的全部鍵。
m1.keySet().removeAll(m2.keySet());
在同一個批量操做中開始混合鍵和值時會發生什麼?假設你有一個Map
,managers
,將公司中的每一個員工映射到員工的經理,咱們會故意模糊鍵和值對象的類型,不要緊,只要它們是相同的,如今假設你想知道全部「我的貢獻者」(或非管理者)是誰,如下代碼段將準確告訴你你想要了解的內容。
Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet()); individualContributors.removeAll(managers.values());
假設你要解僱全部直接向某位經理Simon報告的員工。
Employee simon = ... ; managers.values().removeAll(Collections.singleton(simon));
請注意,這個語法是使用Collections.singleton
,這是一個靜態工廠方法,它返回一個帶有指定元素的不可變Set
。
一旦你完成了這項工做,你可能會有一羣員工,他們的經理再也不爲公司工做(若是任何Simon的直接報告自己就是經理),如下代碼將告訴你哪些員工擁有再也不爲公司工做的經理。
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers); m.values().removeAll(managers.keySet()); Set<Employee> slackers = m.keySet();
這個例子有點棘手,首先,它建立Map
的臨時副本,並從臨時副本中刪除其(manager
)值是原始Map
中的鍵的全部條目,請記住,原始Map
爲每一個員工都有一個條目。所以,臨時Map
中的其他條目包括來自原始Map
的其(經理)值再也不是僱員的全部條目,所以,臨時副本中的鍵剛好表明了咱們正在尋找的員工。
多重映射就像Map
,但它能夠將每一個鍵映射到多個值,Java集合框架不包含多重映射的接口,由於它們並不經常使用。使用Map
值爲List
實例做爲多重映射的Map
是一件至關簡單的事情。下一個代碼示例演示了此技術,該示例讀取每行包含一個單詞(所有小寫)的單詞列表,並打印出符合大小標準的全部變位詞組。變位詞組是一堆單詞,全部單詞都包含徹底相同的字母,但順序不一樣,該程序在命令行上有兩個參數:(1)字典文件的名稱,(2)要打印出的變位詞組的最小尺寸,不打印包含少於指定最小值的單詞組的變位詞組。
找到變位詞組有一個標準技巧:對於字典中的每一個單詞,按字母順序排列單詞中的字母(即,將單詞的字母從新排序爲字母順序)並將條目放入多重映射,將字母順序排列的單詞映射到原始單詞。例如,單詞bad
致使將abd
條目映射爲bad
以將其放入多重映射中,稍做思考就會發現,任何給定鍵映射到的全部單詞都構成一個變位詞組。迭代多重映射中的鍵,打印出符合大小約束的每一個變位詞組是一件簡單的事情。
如下程序是該技術的直接實現。
import java.util.*; import java.io.*; public class Anagrams { public static void main(String[] args) { int minGroupSize = Integer.parseInt(args[1]); // Read words from file and put into a simulated multimap Map<String, List<String>> m = new HashMap<String, List<String>>(); try { Scanner s = new Scanner(new File(args[0])); while (s.hasNext()) { String word = s.next(); String alpha = alphabetize(word); List<String> l = m.get(alpha); if (l == null) m.put(alpha, l=new ArrayList<String>()); l.add(word); } } catch (IOException e) { System.err.println(e); System.exit(1); } // Print all permutation groups above size threshold for (List<String> l : m.values()) if (l.size() >= minGroupSize) System.out.println(l.size() + ": " + l); } private static String alphabetize(String s) { char[] a = s.toCharArray(); Arrays.sort(a); return new String(a); } }
在173,000字的字典文件上運行此程序,最小變位詞組大小爲8會產生如下輸出。
9: [estrin, inerts, insert, inters, niters, nitres, sinter, triens, trines] 8: [lapse, leaps, pales, peals, pleas, salep, sepal, spale] 8: [aspers, parses, passer, prases, repass, spares, sparse, spears] 10: [least, setal, slate, stale, steal, stela, taels, tales, teals, tesla] 8: [enters, nester, renest, rentes, resent, tenser, ternes, treens] 8: [arles, earls, lares, laser, lears, rales, reals, seral] 8: [earings, erasing, gainers, reagins, regains, reginas, searing, seringa] 8: [peris, piers, pries, prise, ripes, speir, spier, spire] 12: [apers, apres, asper, pares, parse, pears, prase, presa, rapes, reaps, spare, spear] 11: [alerts, alters, artels, estral, laster, ratels, salter, slater, staler, stelar, talers] 9: [capers, crapes, escarp, pacers, parsec, recaps, scrape, secpar, spacer] 9: [palest, palets, pastel, petals, plates, pleats, septal, staple, tepals] 9: [anestri, antsier, nastier, ratines, retains, retinas, retsina, stainer, stearin] 8: [ates, east, eats, etas, sate, seat, seta, teas] 8: [carets, cartes, caster, caters, crates, reacts, recast, traces]
許多這些詞彷佛有點虛僞,但這不是程序的錯;它們在字典文件中,這是使用的字典文件,它源自Public Domain ENABLE基準參考詞列表。