在Java中,Map是作为一个顶级接口,构成了集合框架的一个重要分支。本文,将给读者演示如何去使用不同的Map类型,因为在JDK中,Map接口具有HashMap、TreeMap、Hashtable和LinkdedHashMap四个子接口。
在JDK中,一共有多达四种Map接口,它们是HashMap、TreeMap、Hashtable、LinkedHashMap,它们的使用频率都非常地高。如果我们用一句话描述它们的最主要特点:
1. HashMap是基于哈希表(hash table)实现,其keys和values都没有顺序。
2. TreeMap是基于红黑树(red-black tree)实现,按照keys排序元素。
3. LinkedHashMap是基于哈希表(hash table)实现,按照插入顺序排序元素。
4. Hashtable区别与HashMap的地方只有,它是同步的(synchronized),并因此,性能较低些。为了性能,在线程安全的代码中,优先考虑使用HashMap。
我们知道HashMap底层是基于哈希表实现的,而哈希表对于键值有约束。当使用自定义的类型来做HashMap的键时,为了程序的运行结果正确,必须定义该类型的equals()方法和hashCode()方法。
class Dog {
String color;
Dog(String color) {
this.color = color;
}
public String toString() {
return color + " dog";
}
}
public class Main {
public static void main(String[] args) {
HashMap<Dog, Integer> dogMap = new HashMap<Dog, Integer>();
dogMap.put(new Dog("red"), 10);
dogMap.put(new Dog("black"), 15);
dogMap.put(new Dog("white"), 5);
dogMap.put(new Dog("white"), 20);
System.out.println(dogMap.size());
for (Entry<Dog, Integer> entry : dogMap.entrySet()) {
System.out.println(entry.getKey(),toString() + " - " + entry.getValue());
}
}
}
此代码片段的输出结果如下所示:
4
white dog - 5
black dog -15
red dog - 10
white dog - 20
可以发现,我们此处错误地添加了两个“white dogs”到dogMap集合中,但是,dogMap却没有报告错误。实际上,我们希望只能够添加一个“white dogs”到该dogMap中。这种情况下,我们应该重写Dog类的equals()方法和hashCode()方法。
class Dog {
String color;
Dog(String color) {
this.color = color;
}
@Override
public boolean equals(Object o) {
return ((Dog) o).color.equals(this.color);
}
@Override
public int hashCode() {
return color.length();
}
public String toString() {
return color + " dog";
}
}
这样修改以后,我们的输出结果将变成如下所示:
3
red dog - 10
white dog - 20
black dog - 15
这种现象的背后魔法其实很简单。HashMap是不允许存储相同的元素的,但是,HashMap判断元素是否相同的依据就是该元素的键值是否相同。此处我们的键值类型是Dog,而两个Dog对象是否相同,其实是取决于Dog对象的equals()方法和hashCode()方法。对于自定义的类型而言,其默认的equals()方法和hashCode()方法来源与Java中的超级父类java.lang.Object,它们都是只在两个对象引用是同一个对象时,才返回true值。但是,对于Dog对象,我们希望当它们的color一样时,就认为两个Dog是相等的,所以,我们必须重写其equals()方法和hashCode()方法。如果对于equals()方法和hashCode()方法不是很了解,建议参看本站的这篇文章。
TreeMap可以排序元素,问题是,TreeMap排序元素的依据是什么呢?对于Dog类,假设我们有如下的代码片段:
public class Main {
public static void main(String[] args) {
TreeMap<Dog, Integer> dogMap = new TreeMap<Dog, Integer>();
dogMap.put(new Dog("red"), 10);
dogMap.put(new Dog("black"), 15);
dogMap.put(new Dog("white"), 5);
for (Entry<Dog, Integer> entry : dogMap.entrySet()) {
System.out.println(entry.getKey() + " - " + entry.getValue());
}
}
}
此代码片段编译能够通过,但是运行时会出现如下所示的错误:
Exception in thread "main" java.lang.ClassCastException: collection.Dog cannot be cast to java.lang.Comparable
at java.util.TreeMap.put(Unknown Source)
at collection.Main.main(Main.java:35)
出现此错误的原因在于:TreeMap的底层实现是红黑树,并因此能够根据键值来排序元素。所以,TreeMap的键值必须可以相互比较大小,换言之,键值的类型必须实现java.util.Comparable接口。所以,我们应该修改Dog类成为如下所示:
class Dog implements Comparable { String color; int size; Dog(String color, int size) { this.color = color; this.size = size; } @Override public int compareTo(Dog o) { return o.size - this.size; } } public class Main { public static void main(String[] args) { TreeMap<Dog, Integer> dogMap = new TreeMap<Dog, Integer>(); dogMap.put(new Dog("red", 20), 5); dogMap.put(new Dog("black", 30), 10); dogMap.put(new Dog("white", 10), 15); dogMap.put(new Dog("white", 10), 20); for (Entry
entry : dogMap.entrySet()) { System.out.println(entry.getKey().color + " - " + entry.getKey().size + " - " + entry.getValue()); } }
} }
该代码的输出结果是:
white - 10 - 20
red - 20 - 5
black - 30 - 10
注意:我们往dogMap中添加了四个Dog对象,输出结果却显示dogMap只包含三个Dog对象。
结论:TreeMap由于底层实现是红黑树,而HashMap、Hashtable和LinkedHashMap的底层数据结构都是哈希表,所以TreeMap集合要求其键值必须实现Comparable接口,并且使用其作为元素判等和比较大小的唯一依据。而其他三个Map都使用hashCode()方法和equals()方法来判断元素是否相等。
Hashtable与HashMap的区别仅仅是:
1. HashTable是同步的(synchronized)。
2. 不允许有 null 值
LinkedHashMap是HashMap的子类,所以通过继承机制,拥有了HashMap的所有特性。而且,它还增加了保持元素插入顺序的特性。
JDK的集合框架中对于Map的分支,Class的层次结构比较简单,容易掌握。但是,需要注意TreeMap在底层实现上与其他三种Map的本质区别,并由此带来的独特性。
转载自(对原文某些输出结果加以更正):http://www.tiantianbianma.com/java-hashmap-treemap-hashtable-linkedhashmap.html/