标签:
很多东西都是大处显积累,小处见功力,来点功力。
hashCode跟equals 相伴相生,所以要一起讨论才有意义。
在java中,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,就是说当集合中插入对象时,怎么分辨该对象是否已经存在。按照正常思路,应该是依次进行equals比较,但是其实效率不高,java中的做法是先比较hashCode,如果相同在equals比较,到这里,疑问就出来了,为什么会比较hashCode,hashCode相同的情况下为什么equals为什么还会不同?
具体来讲,hashCode就类似于数据结构里面的hash算法,通过散列的方式存放数据,那么都知道hash算法会产生冲突,于是,就会有不同的数据算出来是相同的hashCode,由于Object的hashCode方法是一个native的,C++写的,所以,直接看String重写的,
1 /** 2 * Returns a hash code for this string. The hash code for a 3 * <code>String</code> object is computed as 4 * <blockquote><pre> 5 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 6 * </pre></blockquote> 7 * using <code>int</code> arithmetic, where <code>s[i]</code> is the 8 * <i>i</i>th character of the string, <code>n</code> is the length of 9 * the string, and <code>^</code> indicates exponentiation. 10 * (The hash value of the empty string is zero.) 11 * 12 * @return a hash code value for this object. 13 */ 14 public int hashCode() { 15 int h = hash; 16 if (h == 0 && value.length > 0) { 17 char val[] = value; 18 19 for (int i = 0; i < value.length; i++) { 20 h = 31 * h + val[i]; 21 } 22 hash = h; 23 } 24 return h; 25 }
算法很简单,他并不是我们想到的内存地址,而是根据字符串内容算出来的,是一个int类型的数字,显然,不同字符串肯定会出现相同的hashCode,那么这个时候,处理冲突的方法就很关键,java中使用的是拉链法,就是每个同义词进行拉链,所以,一个hashcode会对应多个不同的对象,那么,这时候就明白了,为什么后面还会用equals继续进行判断。
再看String重写的equals方法
1 /** 2 * Compares this string to the specified object. The result is {@code 3 * true} if and only if the argument is not {@code null} and is a {@code 4 * String} object that represents the same sequence of characters as this 5 * object. 6 * 7 * @param anObject 8 * The object to compare this {@code String} against 9 * 10 * @return {@code true} if the given object represents a {@code String} 11 * equivalent to this string, {@code false} otherwise 12 * 13 * @see #compareTo(String) 14 * @see #equalsIgnoreCase(String) 15 */ 16 public boolean equals(Object anObject) { 17 if (this == anObject) { 18 return true; 19 } 20 if (anObject instanceof String) { 21 String anotherString = (String) anObject; 22 int n = value.length; 23 if (n == anotherString.value.length) { 24 char v1[] = value; 25 char v2[] = anotherString.value; 26 int i = 0; 27 while (n-- != 0) { 28 if (v1[i] != v2[i]) 29 return false; 30 i++; 31 } 32 return true; 33 } 34 } 35 return false; 36 }
他是对比每个位置上的字符,当然比hashcode算法更严格。
实际来看HashMaP中是如何使用这两个的
1 /** 2 * Associates the specified value with the specified key in this map. 3 * If the map previously contained a mapping for the key, the old 4 * value is replaced. 5 * 6 * @param key key with which the specified value is to be associated 7 * @param value value to be associated with the specified key 8 * @return the previous value associated with <tt>key</tt>, or 9 * <tt>null</tt> if there was no mapping for <tt>key</tt>. 10 * (A <tt>null</tt> return can also indicate that the map 11 * previously associated <tt>null</tt> with <tt>key</tt>.) 12 */ 13 public V put(K key, V value) { 14 if (table == EMPTY_TABLE) { 15 inflateTable(threshold); 16 } 17 if (key == null) 18 return putForNullKey(value); 19 int hash = hash(key); 20 int i = indexFor(hash, table.length); 21 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 22 Object k; 23 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 24 V oldValue = e.value; 25 e.value = value; 26 e.recordAccess(this); 27 return oldValue; 28 } 29 } 30 31 modCount++; 32 addEntry(hash, key, value, i); 33 return null; 34 }
很显然,就是线比较hashCode,在比较equals,如果相同,则覆盖之前放入的(这一点很关键)。
好,基本原理说完了,就开始说关键的使用,看下面的例子
1 package com.cxh.test1; 2 3 import java.util.HashMap; 4 import java.util.HashSet; 5 import java.util.Set; 6 7 8 class People{ 9 private String name; 10 private int age; 11 12 public People(String name,int age) { 13 this.name = name; 14 this.age = age; 15 } 16 17 public void setAge(int age){ 18 this.age = age; 19 } 20 21 @Override 22 public boolean equals(Object obj) { 23 // TODO Auto-generated method stub 24 return this.name.equals(((People)obj).name) && this.age== ((People)obj).age; 25 } 26 } 27 28 public class Main { 29 30 public static void main(String[] args) { 31 32 People p1 = new People("Jack", 12); 33 System.out.println(p1.hashCode()); 34 35 HashMap<People, Integer> hashMap = new HashMap<People, Integer>(); 36 hashMap.put(p1, 1); 37 38 System.out.println(hashMap.get(new People("Jack", 12))); 39 } 40 }
结果返回的是null,原因是什么?就是重写了equals,但是没有重写hashCode方法,这样,还没到判断equals呢,已经通过hashCode给否定了。所以,谨记一点,重写equals,一定同时重写hashCode。
上面可以重写的hashCode方法可以为:
1 @Override 2 public int hashCode() { 3 // TODO Auto-generated method stub 4 return name.hashCode()*37+age; 5 }
下面这段话摘自Effective Java一书:
对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:
“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。
其实最后这句最重要的还有一点,就是调用remove的时候,如果,hashCode变了,很容易出现无法remove的情况,那么接下来的结果就是内存泄漏。
标签:
原文地址:http://www.cnblogs.com/Xdwd/p/5674691.html