8.6.2 Java 8改进的HashMap和Hashtable实现类

8.6.2 Java 8改进的HashMap和Hashtable实现类

HashMapHashtable都是Map接口的典型实现类,它们之间的关系完全类似于ArrayListVector的关系: Hashtable是一个古老的Map实现类,它从JDK1.0起就已经出现了,当**Hashtable出现时,Java还没有提供Map接口**,所以Hashtable包含了两个烦琐的方法:

  • elements()方法(这个类似于Map接口定义的values()方法)
  • keys()方法(该方法类似于Map接口定义的keySet()方法)

现在很少使用这两个方法.

Java 8改进了HashMap的实现,使用HashMap存在key冲突时依然具有较好的性能.

Hashtable和HashMap的典型区别

除此之外, HashtableHashMap存在两点典型区别。

Hashtable线程安全 HashMap线程不安全

  • Hashtable是一个线程安全Map实现,但HashMap线程不安全的实现,所以HashMapHashtable的性能高一点;但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好。

Hashtable的key和value不能为null HashMap的key和value可以为null

  • Hashtable不允许使用null作为keyvalue,如果试图把null值放进Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为keyvalue

由于**HashMap里的key不能重复,value可以重复**,所以HashMap里最多只有一个key-value对的keynull但可以有无数多个key-value对的valuenull

下面程序示范了用null值作为HashMapkeyvalue的情形。

实例 null作为HashMap的key和value的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.*;

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对的keynull值,所以无法再放入keynull值的key-value对。②代码处可以放入该key-value对,因为一个HashMap中可以有多个valuenull。编译、运行上面程序,看到如下输出结果:

1
{null=null, a=null}

尽量少用Hashtable

Hashtable的类名上就可以看出它是一个古老的类,它的命名甚至没有遵守Java的命名规范:每个单词的首字母都应该大写。也许当初开发Hashtable的工程师也没有注意到这一点,后来大量Java程序中使用了Hashtable类,所以这个类名也就不能改为HashTable了,否则将导致大量程序需要改写。与Vector类似的是,尽量少用Hashtable实现类,

即使有线程安全要求也不要使用Hashtable

即使需要创建线程安全的Map实现类,也无须使用Hashtable实现类,可以通过后面介绍的Collections工具类把HashMap变成线程安全的。

HashMap中作为key对象要满足什么条件

为了成功地在HashMapHashtable中存储、获取对象,用作key的对象必须实现hashCode()方法和equals()方法。

HashMap判断两个key相等的标准是什么

HashSet集合不能保证元素的顺序一样, HashMapHashtable也不能保证其中key-value对的顺序。类似于HashSet, HashMapHashtable判断两个key相等的标准也是:两个key通过equals方法比较返回true,并且两个keyhashCode值也相等

HashMap判断两个value相等的标准是什么

除此之外, HashMapHashtable中还包含一个containsValue()方法,用于判断是否包含指定的value那么HashMapHashtable如何判断两个value相等呢? HashMapHashtable判断两个value相等的标准更简单:只要两个对象通过equals方法比较返回true即可

程序示例

下面程序示范了Hashtable判断两个key相等的标准和两个value相等的标准。

1
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;

class A
{
int count;
public A(int count)
{
this.count = count;
}
// 根据count的值来判断两个对象是否相等。
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (obj != null && obj.getClass() == A.class)
{
A a = (A)obj;
return this.count == a.count;
}
return false;
}
// 根据count来计算hashCode值。
public int hashCode()
{
return this.count;
}
}
class B
{
// 重写equals()方法,B对象与任何对象通过equals()方法比较都返回true
public boolean equals(Object obj)
{
return true;
}
}
public class HashtableTest
{
public static void main(String[] args)
{
Hashtable ht = new Hashtable();
ht.put(new A(60000) , "疯狂Java讲义");
ht.put(new A(87563) , "轻量级Java EE企业应用实战");
ht.put(new A(1232) , new B());
System.out.println(ht);
// 只要两个对象通过equals比较返回true,
// Hashtable就认为它们是相等的value。
// 由于Hashtable中有一个B对象,
// 它与任何对象通过equals比较都相等,所以下面输出true。
System.out.println(ht.containsValue("测试字符串")); // ① 输出true
// 只要两个A对象的count相等,它们通过equals比较返回true,且hashCode相等
// Hashtable即认为它们是相同的key,所以下面输出true。
System.out.println(ht.containsKey(new A(87563))); // ② 输出true
// 下面语句可以删除最后一个key-value对
ht.remove(new A(1232)); //③
System.out.println(ht);
}
}

上面程序定义了A类和B类,其中

  • A类判断两个A对象相等的标准是count实例变量:只要两个A对象的count变量相等,则通过equals方法比较它们返回true,它们的hashCode值也相等;
  • 而B对象则可以与任何对象相等。

Hashtable判断value相等的标准是: value与另外一个对象通过equals()方法比较返回true即可。上面程序中的ht对象中包含了一个B对象,它与任何对象通过equals()方法比较总是返回true,所以在①代码处返回true在这种情况下,不管传给ht对象的containtsValue()方法参数是什么,程序总是返回true.
根据Hashtable判断两个key相等的标准,程序在②处也将输出true,虽然两个A对象虽然不是同个对象,但它们**通过equals方法比较返回tue,且hashCode值相等, Hashtable就认为它们是同一个key**。类似的是,程序在③处也可以删除对应的key-value对。

equals方法和hashCode方法判断标准要一致

当使用自定义类作为HashMapHashtablekey时,如果重写该类的equals()方法和hashCode()方法,则应该保证两个方法的判断标准一致,也就是当两个key通过equals方法比较返回true时,两个keyhashCode()返回值也应该相同。因为HashMapHashtable保存key的方式与HashSet保存集合元素的方式完全相同,所以HashMapHashtablekey的要求与HashSet对集合元素的要求完全相同。

可变对象作为HashMap的key时可能无法正确访问

HashSet类似的是,如果使用可变对象作为HashMapHashtablekey,并且程序修改了作为key的可变对象,则也可能出现与HashSet类似的情形:程序再也无法准确访问到Map中被修改过的key。看下面程序

1
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
import java.util.*;

public class HashMapErrorTest
{
public static void main(String[] args)
{
HashMap ht = new HashMap();
// 此处的A类与前一个程序的A类是同一个类
ht.put(new A(60000) , "疯狂Java讲义");
ht.put(new A(87563) , "轻量级Java EE企业应用实战");
// 获得Hashtable的key Set集合对应的Iterator迭代器
Iterator it = ht.keySet().iterator();
// 取出Map中第一个key,并修改它的count值
A first = (A)it.next();
first.count = 87563; // ①
// 输出{A@1560b=疯狂Java讲义, A@1560b=轻量级Java EE企业应用实战}
System.out.println(ht);
// 只能删除 没有被修改过 的key所对应的key-value对
ht.remove(new A(87563));

System.out.println(ht);
// 无法获取剩下的value,下面两行代码都将输出null。
System.out.println(ht.get(new A(87563))); // ② 输出null
System.out.println(ht.get(new A(60000))); // ③ 输出null
}
}

该程序使用了前一个程序定义的A类实例作为key,而A对象是可变对象。当程序在①处修改了A对象后,实际上修改了HashMap集合中元素的key,这就导致该key不能被准确访问。当程序试图删除count87563的A对象时,只能删除没被修改的key所对应的key-value对。程序②和③处的代码都不能访问”疯狂Java讲义“字符串,这都是因为它对应的key被修改过的原因。

尽量不要使用可变对象作为HashMap的key

HashSet类似的是,尽量不要使用可变对象作为HashMapHashtablekey,如果确实需要使用可变对象作为HashMapHashtablekey,则尽量不要在程序中修改作为key的可变对象。