首页 > 代码库 > 容器深入研究 --- 散列与散列码(一)

容器深入研究 --- 散列与散列码(一)

通常的:

当标准类库中的类被作用HashMap的键。它用的很好,因为它具备了键所需的全部性质

当你自己创建用作HashMap的键的类,有可能会忘记在其中放置必须的方法,而这时通常会犯的一个错误。

例如:考虑一个天气系统,将Groundhog对象与Prediction对象联系起来。

class Groundhog {

	protected int number;
	
	public Groundhog(int n) {
		number = n;
	}
	
	public String toString() {
		return "Groundhog # " + number;
	}
}

class Prediction {
	private static Random rand = new Random(47);
	
	private boolean shadow = rand.nextDouble() > 0.5;
	
	@Override
	public String toString() {
		if (shadow) {
			return "Six more weks of Winter";
		} else {
			return "Early Spring";
		}
	}
}


public class Main {

	public static <T extends Groundhog> void detectSpring(Class<T> type) throws Exception {
		Constructor<T> ghog = type.getConstructor(int.class);
		Map<Groundhog, Prediction> map = new HashMap<Groundhog, Prediction>();
		for (int i = 0; i < 10; i++) {
			map.put(ghog.newInstance(i), new Prediction());
			System.out.println("map : " + map);
		}
		Groundhog gh = ghog.newInstance(3);
		System.out.println("Looking up prediction for " + gh);
		if (map.containsKey(gh)) {
			System.out.println(map.get(gh));
		} else {
			System.out.println("Key not found : " + gh);
		}
	}
	
	public static void main(String[] args) throws Exception {
		map : {Groundhog # 0=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 8=Six more weks of Winter, Groundhog # 5=Early Spring}
		map : {Groundhog # 0=Six more weks of Winter, Groundhog # 3=Early Spring, Groundhog # 4=Six more weks of Winter, Groundhog # 2=Early Spring, Groundhog # 9=Six more weks of Winter, Groundhog # 7=Early Spring, Groundhog # 6=Early Spring, Groundhog # 1=Six more weks of Winter, Groundhog # 8=Six more weks of Winter, Groundhog # 5=Early Spring}
		Looking up prediction for Groundhog # 3
		Key not found : Groundhog # 3
		
		detectSpring(Groundhog.class);
	}
}

首先会使用Groundhog和与之相关联的Prediction填充HashMap,然后打印此HashMap,以便观察它是否被填入了一些内容。然后使用标识数组3作为Groundhog的主键,查找与之对应的内容。

但是,它无法找到数字3这个键。问题出在Groundhog自动继承基类Object,所以这里使用Object的hashCode()生成散列码,而它默认的使用对象的地址计算散列码。因此,由Groundhog(3)生成的第一个实例的散列码与由Groundhog的第二个实例的散列码是不同的,而我们正是使用的后者进行查找的。

注意的:

可能你会认为,只需要编写恰当的hashCode()方法的覆盖版本即可。但是它仍然无法正常运行,除非你同时覆盖equals()方法,它也是Object的一部分。HashMap使用equals()判断判断当前的键是否与表中的键相同。

正确的equals()方法必须满足下列五个条件:

1,自反性。对任意x,x.equals(x)一定返回true。

2,对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。

3,传递性。对任意x、y、z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。

4,一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用多少次x.equals(y),返回的 结果应该保持一致,要么一直是true,要么一直是false。

5,对任何不适null的x,x.equals(null)一定返回false。

再次强调,默认的Object.equals()只是比较对象的地址,所以一个Groundhog(3)并不等于另一个Groundhog(3)。因此,要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()。

public class Groundhog2 extends Groundhog {
		public Groundhog2(int n) {
			super(n);
		}
		public int hashCode() { reutrn number;}
		public boolean equals(Object o) {
			return o instanceof Groundhog2 && (number == ((Groundhog2)o).number);
		}
	}

另外需要注意的:

Groundhog2.hashCode()返回Groundhog的标识数字作为散列码。在此例中,程序员负责确保不同的Groundhog具有不同的编号。hashCode()并不需要总是返回唯一的标识码,但是equals()方法必须严格的判断两个对象是否相同。尽管看起来equals()方法只是检查其参数是否是Groundhog2的实例,但是instanceof悄悄的检查了此对象是否为null,因为如果instanceof左边的参数为null,它会返回false。

理解hashCode()

前面的例子只是正确解决问题的第一步。它只说明,如果不为你的键覆盖hashCode()和equals(),那么使用散列的数据结构(HashSet,HashMap,LinkedHashSet或LinkedHashMap)就无法正确处理你的键。然而,要很好的解决此问题,你必须了解这些数据结构的内部结构。

首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。不过使用TreeMap或者你自己实现的Map也可以达到此目的。与散列实现相反,下面的示例用一对ArrayList实现了一个Map。

class MapEntry<K,V> implements Map.Entry<K, V> {

	private K key;
	private V value;
	
	public MapEntry(K key, V value) {
		this.key = key;
		this.value = http://www.mamicode.com/value;>
Map.entrySet()方法必须产生一个Map.Entry对象集。但是,Map.Entry是一个接口,用来描述依赖于实现的结构,因此如果你想要创建自己的Map类型,就必须同时定义Map.Entry的实现。