Map 用于保存具有映射关系的数据,因此 Map 集合保存着两组值,一组值用于保存 Map 里的 key,另外一组值用于保存 Map 里的 value,key 和 value 都可以是任何引用类型的数据。Map 的 key 不允许重复,即同一个 Map 对象的任何两个 key 通过 equals 方法比较总是返回 false。
key 和 value 之间存在单向一对一关系,即通过制定的 key,总能找到唯一的、确定的 value。从 Map 中取出 数据时,只要给出指定的 key,就可以取出对应的 value。如果把 Map 的两值拆开来看,Map 里的数据如图 8.7 所示的结构。
从图 8.7 中可以看出,如果把 Map 里的所有 key 放在一起来看,它们就组成一个 Set 集合(所有的 key 没有顺序,key 与 key 之间不能重复),实际上 Map 确实包含了一个 keySet() 方法,用于返回 Map 里所有 key 组成的 Set 集合。
不仅如此,Map 里 key 集和 Set 集合里元素的存储形式也很像,Map 之类和 Set 子类子啊名字上也惊人地相似,比如 Set 接口下有 HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet 等子接口和实现类,而 Map 接口下则有 HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap 等子接口和实现类。正如它们的名字所暗示的,Map 的这些实现类和子接口中 key 集的存储形式和对应 Set 集合中元素的存储形式完全相同。
Set 和 Map 之间的关系非常密切。虽然 Map 中放的元素是 key-value 对,Set 集合中放的元素时单个对象,但如果我们把 key-value 对中的 value 当成 key 的附庸:key 在哪里,value 就跟在哪里,这样就可以像对待 Set 一样对待 Map 了。事实上,Map 提供了一个 Entry 内部类来封装 key-value 对,而计算 Entry 存储时则只考虑 Entry 封装的 key,从 Java 源代码来看,Java 是先实现了 Map,然后通过包装一个所有 value 都为 null 的 Map 就实现了 Set 集合。
如果把 Map 里的所有 value 放在一起来看,它们又非常类似与一个 List: 元素与元素之间可以重复,每个元素可以根据索引来查找,只是 Map 中的索引不再使用整数值,而是以另一个对象作为索引。如果需要从 List 集合中取出元素,则需要提供该元素的数字索引;如果需要从 Map 中取出元素,则需要提供该元素的 key 索引。因此, Map 有时也被称为字典,或关联数组。Map 接口中定义了如下常用的方法。
void clear(): 删除该 Map 对象中的所有 key-value 对。
boolean containsKey(Object key): 查询 Map 中是否包含指定的 key,如果包含则返回 true。
boolean containsKey(Object value):查询 Map 中是否包含一个或多个 value,如果包含则返回 true。
Set entrySet(): 返回 Map 中包含的 key-value 对所组成的 Set 集合,每个集合元素都是 Map.Entry(Entry 是 Map 的内部类)对象。
Object get(Object key): 返回指定 key 所对应的 value;如果此 Map 中不包含该 key,则返回 null。
boolean isEmpty(): 查询该 Map 是否为空(即不包含任何 key-value 对),如果为空则返回 true。
set keySet(): 返回该 Map 中所有 key 组成的 Set 集合。
Object put(Object key, Object value): 添加一个 key-value 对,如果当前 Map 中已有一个与该 key 相等的 key-value 对,则新的 key-value 对会覆盖原来的 key-value 对。
void putAll(Map m): 将指定 Map中的 key-value 对复制到本 Map 中。
Object remove(Object key): 删除指定 key 所对应的 key-value 对,返回被删除 key 所关联的 value,如果该 key 不存在,则返回 null。
int size(): 返回该 Map 里的 key-value 对的个数。
Collection values(): 返回该 Map 里所有 value 组成的 Collection。
Map 接口提供了大量的实现类,典型实现如 HashMap 和 Hastable 等、HashMap 的子类 LinkedHashMap,还有 SortedMap 子接口及该接口的实现类 TreeMap,以及 WeakHashMap、IdentityHashMap 等。下面将详细介绍 Map 接口实现类。
Map 中包括一个内部类 Entry,该类封装了一个 key-value 对。Entry 包含如下三个方法。
Objet getKey(): 返回该 Entry 里包含的 key 值。
Object getValue(): 返回该 Entry 里包含的 value 值。
Object setValue(V value): 设置该 Entry 里包含的 value 值,并返回新设置的 value 值。
1,HashMap 和 Hashtable 实现类
HashMap 和 Has和他爸了 都是 Map 接口的典型实现类,它们之间的关系完全类似于 ArrayList 和 Vector 的关系:Hastable 是一个古老的 Map 实现类,它从 JDK 1.0 其就已经出现了,当它出现时,Java 还没有提供 Map 接口,所以它包含了两个烦琐的方法,即 elements()(类似于 Map 接口定义的 values() 方法)和 keys() (类似于 Map 接口定义的 keySet() 方法),现在很少使用这个两个方法了。
除此之外,Hashtable 和 HashMap 存储两点典型区别。
Hashtable 是一个线程安全的 Map 实现,但 HashMap 是线程不安全的实现,所以 HashMap 比 Hashtable 的性能高一点:但如果有多个线程访问同一个 Map 对象时,使用 Hashtable 实现类会更好。
Hashtable 不允许使用 null 作为 key 和 value,如果试图把 null 值放进 Hashtable 中,将会引发 NullPointerException 异常;但 HashMap 可以使用 null 作为 可以或 value。
由于 HashMap 里的 key 不能重复,所以 HashMap 里最多只有一个 key-value 对的 key 为 null,但可以有无数多个 key-value 对的 value 为 null。下面程序示范了用 null 值作为 HashMap 的 可以 和 value 的情形。
package com.sym.demo2;
import java.util.HashMap;
public class NullInHashMap {
public static void main(String[] args) {
HashMap hm = new HashMap();
//试图将两个 key 为 null 值的 key-value 对放入 HashMap 中
hm.put(null, null);
hm.put(null, null);//①
//将一个 value 为 null 值的 key-value 对放入 HashMap 中
hm.put("a", null);//②
//输出 Map 对象
System.out.println(hm);
}
}
上面程序试图向 HashMap 中放入三个 key-value 对,其中①代码处无法将 key-value 对放入,因为 Map 中已经有一个 key-value 对的 key 为 null 值,所以无法再放入 key 为 null 的 key-value 对。②代码出可以放入该 key-value 对,因为一个 HashMap 中 可以有多个 value 为 null 值。编译、运行上面程序,看到如下输出结果:
{null=null, a=null}
根据上面结果可以看出,HashMap 重写了 toString() 方法,实际上所有的 Map 实现类都重写了 toString() 方法,调用 Map 对象的 toString() 方法总是返回如下格式的字符串:{key1=value1,key2=value2...}
从 Hashtable 的类名上就可以看出它是一个古老的类,它的命名甚至没有遵守 Java 的命名规范。与 Vector 类似的是,尽量少用 Hashtable 实现类,即使需要创建线程安全的 Map 实现类,也无须使用 Hashtable 实现类,可以通过后面介绍的 Collections 工具类 把 Ha