一个Map
是一个键值对对象。
一个map不能包含重复的键:每个键最多可以映射到一个值,它对数学函数抽象进行了建模。
put
get
remove
containsKey
containsValue
size
empty
putAll
clear
keySet
entrySet
values
Java平台包含三种Map实现:HashMap(无序)、TreeMap(按键的字母顺序顺序)和LinkedHashMap(原顺序),它们的行为和性能类似于HashSet、TreeSet和LinkedHashSet,如 The Set Interface一节所述。
JDK8聚合操作收集Map的例子。
在面向对象编程中,对现实世界的对象进行建模是一项日常操作。
下面的例子按部门对员工进行分组:
// Group employees by department Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment));
按部门统计薪资:
// Compute sum of salaries by department Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.summingInt(Employee::getSalary)));
按成绩及格与否,将学生分组:
// Partition students into passing and failing Map<Boolean, List<Student>> passingFailing = students.stream() .collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD));
按城市进行分组:
// Cascade Collectors Map<String, Map<String, List<Person>>> peopleByStateAndCity = personStream.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)))
Map的基本操作(put、get、containsKey、containsValue、size和isEmpty)与Hashtable中的对应操作完全相同。
一个单词频次表的例子:
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); } }
与set和Listinterfaces类似,Map加强了对equals和hashCode方法,以便比较两个Map对象的逻辑相等性,而不必考虑它们的实现类型。两个Map的键值都相等,则两者相等。
通常所有通用Map实现的构造函数接受Map对象并初始化新Map,相当于复制一个相同的Map。这个标准Map转换构造函数完全类似于标准Collection 构造函数。
Map<K, V> copy = new HashMap<K, V>(m);
clear 删除所有元素
putAll 类似于Collection的addAll,与构造函数一起使用可实现按默认值创建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 views 方法可以把Map视为Collection:
for (KeyType key : m.keySet()) System.out.println(key);
iterator
// 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总是返回相同的对象,这正是java中所有Map实现在
java.util
中做的。
三种Collection 视图中,Iterator的 remove 和 entrySet视图的Map.Entry’s setValue 是Map迭代过程中的安全操作,其他操作行为可能是不明确的。
Collection视图支持移除操作, remove, removeAll, retainAll和 clear 。
在任何情况下,Collection视图都不支持添加元素,因为Map的put和putAll方法已经提供了相同的功能。
判断一个Map是否为另一个Map的子集,即包含所有键值对:
if (m1.entrySet().containsAll(m2.entrySet())) { ... }
判断两个Map是否相同:
if (m1.keySet().equals(m2.keySet())) { ... }
假设你有一个Map和两个分别表示必须属性和允许属性的Set,判断Map是否符合限制:
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公共的key:
Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet()); commonKeys.retainAll(m2.keySet());
之前的所有操作都没有改变原Map结构的,但有操作会改变原Map,比如,删除一个Map中它和另一个Map的相同键值对:
m1.entrySet().removeAll(m2.entrySet());
比如,删除一个Map中它和另一个Map的相同键:
m1.keySet().removeAll(m2.keySet());
假设有一个Map,映射员工和管理者的关系,找出没有管理者的员工:
Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet()); individualContributors.removeAll(managers.values());
开除Simon的所有下属:
Employee simon = ... ; managers.values().removeAll(Collections.singleton(simon));
所属管理者不再为公司工作的员工:
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers); m.values().removeAll(managers.keySet()); Set<Employee> slackers = m.keySet();
multimap类似于Map,但它可以将每个键映射到多个值。
multimap类似于Map,但它可以将每个键映射到多个值。Java Collections框架没有包含用于multimap的接口,因为它们并不经常使用。把值类型为List的Map用作multimap,是一件相当简单的事情。下一个代码示例将演示这种技术,该代码示例读取每行包含一个单词的单词列表(全部小写),并打印出满足大小标准的所有字谜组。字谜组是一堆单词,它们都包含完全相同的字母,但顺序不同。
该程序在命令行上接受两个参数:(1)字典文件的名称和(2)要打印的字谜组的最小大小。包含少于指定最小字数的字谜组不会被打印。
有一个查找字谜组的标准技巧:对于字典中的每个单词,将单词中的字母按字母顺序排列(即将单词的字母按字母顺序重新排列),并将一个条目放入multimap中,将按字母顺序排列的单词映射到原始单词。例如,bad这个词会导致将abd映射为bad的条目被放入multimap中。稍作思考,你就会发现,所有的字词,任何给定的关键图都可以组成一个字词组合。遍历multimap中的键,打印出满足大小约束的每个字谜组,是一件很简单的事情。
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); } }