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基准参考词列表。