# Map 接口
Map 是将键映射到值的对象。map 不能包含重复的键:每个键最多可以映射一个值。
它模拟数学函数抽象。所述 Map 接口包括用于基本操作的方法(例如 put,get,remove, containsKey,containsValue,size,和 empty), 批量操作(例如 putAll 和 clear),和集合视图(例如 keySet,entrySet 和 values)。
Java 平台包含三个通用 Map 实现: HashMap、TreeMap 和 LinkedHashMap。他们的行为和性能正是类似于 HashSet,TreeSet 以及 LinkedHashSet.
本页的 Map 其余部分详细讨论了接口。Jdk8 stream 对 map 提供了聚合操作,Collectors.groupingBy,Collectors.summingInt 分组,汇总等。
# map 的基本操作
Map 的基本操作(put,get,containsKey,containsValue,size 和 isEmpty)表现得完全像 Hashtable。
下来来一个示例:统计单词出现的频率次数:
public void test() {
String[] arrs = {"1", "2", "3", "4", "5", "1","5"};
Map<String, Integer> freqs = new HashMap<>();
for (String a : arrs) {
Integer freq = freqs.get(a);
freqs.put(a, freq == null ? 1 : freq + 1);
}
System.out.println("不同的单词有:" + freqs.size()); //不同的单词有:5
System.out.println(freqs); // {3=1, 2=1, 1=2, 5=2, 4=1}
}
2
3
4
5
6
7
8
9
10
11
HashMap 能被替换成 TreeMap,将会看到按单词自然顺序排序的输出。
也可以替换成 LinkedHashMap,按单词出现的顺序 输出。
这种灵活性提供了基于接口的框架有力的说明。
像 Set 和 List 接口一样,Map 加强对 equals 和 hashCode 方法的要求,使得 Map 可以比较两个对象的逻辑相等性, 而不考虑它们的实现类型。如果它们代表相同的键-值映射关系的实例是相等的。
按照惯例,所有通用 Map 实现提供构造函数,它接受一个 Map 对象并初始化新的 Map 以包含指定的所有键值映射 Map。 这个标准 Map 转换构造函数完全类似于标准 Collection 构造函数:它允许调用者创建 Map 一个期望的实现类型, 最初包含另一个实例类型中的所有映射 Map,而不考虑其他 Map 实现类型。
例如,假设你有一个 Map,命名 m。以下一行创建一个新的,HashMap 最初包含所有相同的键值映射m。
Map<K, V> copy = new HashMap<K, V>(m);
# map 接口的批量操作
clear,移除此集合的所有映射。putAll 从指定映射中将所有映射关系复制到此映射中(可选操作)。
以下是演示处理默认值的技巧:
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;
}
2
3
4
5
# 集合视图(Collection Views)
运行 Map 被视为一个 collection
视图的三种方法:
- keySet : set 视图,返回 map 中所有的 key
- values : Collection 视图,返回 map 中所有的 value,因为多个 key 可以隐身到相同的值
- entrySet : set 视图,元素为 Map.Entry 类型
集合视图提供 map 遍历的唯一手段
for (KeyType key : m.keySet())
System.out.println(key);
2
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();
2
3
4
5
for (Map.Entry<KeyType, ValType> e : m.entrySet())
System.out.println(e.getKey() + ": " + e.getValue());
2
很多人担心这些语法可能很慢,因为每次调用视图操作 Map 都必须创建一个新 Collection 实例。 但是没有理由,Map 每当请求 Collection 的时候都返回相同的对象。
!这些返回的视图同样是会反应到源 map 上的。
# 喜欢集合的观点:map 代数?
批量操作(containsAll,removeAll,和retainAll)会很方便。
boolean containsAll(Collection<?> c)
如果此 set 包含指定 collection 的所有元素,则返回 true。
按照类似的方式,假设你想知道两个 Map 对象是否包含所有相同的键映射
if (m1.keySet().equals(m2.keySet())) {
...
}
2
3
# 检验必须的和允许的 key 属性示例:
@Test
public void test() {
String[] arrs = {"1", "2", "3", "4", "5", "1"};
Map<String, String> attrMap = new HashMap<>();
for (String a : arrs) {
attrMap.put(a, a);
}
// 必须的属性
Set<String> requiredAttrs = new HashSet<>();
requiredAttrs.add("7");
// 允许的属性
Set<String> permittedAttrs = new HashSet<>();
permittedAttrs.add("4");
validate(attrMap, requiredAttrs, permittedAttrs);
}
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
输出
Missing attributes: [7]
Illegal attributes: [3, 2, 1, 5]
2
# 假设你想知道两个 Map 的交集
Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet());
commonKeys.retainAll(m2.keySet());
2
上面几个示例都是非破坏性的,也就是操作后不会反应到源 map 中,这里有几个破坏性的:
# 假设你要删除 Map 与另一个 map 共同的键值对
m1.entrySet().removeAll(m2.entrySet());
m1.keySet().removeAll(m2.keySet());
2
3
# 假设想要移除一个经理
Employee simon = ... ;
managers.values().removeAll(Collections.singleton(simon));
2
注意,使用 Collections.singleton 一个静态工厂方法,返回一个不可变 Set 的单个指定的元素。
# 或则从这样移除
Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers);
m.values().removeAll(managers.keySet());
Set<Employee> slackers = m.keySet();
2
3
这个有什么用?创建一个临时副本,再操作临时副本不会反应到源 map 上去吧?
这些示例是不符合实际的,只是演示使用方法。
# multimap
就是一个键对应多个值,很容易实现,例如: Map<String, List<String>> m = new HashMap<String, List<String>>()
;