您的位置:首页 > 编程语言 > Java开发

对java中equals和hashCode函数的一些理解

2011-09-26 11:59 351 查看
如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。
http://blog.csdn.net/RichardSundusky/article/details/1508028
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

java.lang.Object中对hashCode的约定:

在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

看个不改写hashCode导致使用hashMap不能出现预期结果的例子:

public final class PhoneNumber{

private final short areaCode;

private final short exchange;

private final short extension;

public PhoneNumber(int areaCode,int exchage,int extension){

rangeCheck(areaCode,999,"area code");

rangeCheck(exchange,999,"exchange");

rangeCheck(extension,9999,"extension");

this.areaCode=(short) areaCode;

this.exchange=(short) exchange;

this.extension=(short)extension;

}

private static void rangeCheck(int arg,int max, String name){

if(arg<0 || arg>max) throw new IllegalArgumentException(name+":"+arg);

}

public boolean equals(Object o){

if (o == this) reutrn true;

if (!(o instanceof PhoneNumber)) return false;

PhoneNumber pn=(PhoneNumber)o;

return pn.extension==extension && pn.exchange=exchange && pn.areaCode=areaCode;

}

//No hashCode method

...

}

现在有以下几行程序:

Map m=new HashMap();

m.put(new PhoneNumber(1,2,3),"Jenny");

则m.get(new PhoneNumber(1,2,3))的返回值什么?

  虽然这个实例据equals是相等的,但由于没改写hashCode而致两个实例的散列码并不同(即违反第二条要求),因则返回的结果是null而不是"Jenny".

  理想情况下,一个散列函数应该把一个集合中不相等的实例均匀地分布到所有可能的散列值上,下面是接近理想的“处方”:

把某个非零常数值(如17)保存在一个叫result的int类型的变量中;

对于对象中每个关键字域f(指equals方法中考虑的每一个域),完成以下步骤:

为该域计算int类型的散列码c:

如果该域是bloolean类型,则计算(f?0:1)

如果该域是byte,char,short或int类型,则计算(int)f

如果该域是long类型,则计算(int)(f^(>>>32))

如果该域是float类型,则计算Float.floatToIntBits(f)

如果该域是double类型,则计算Double.doubleToLongBits(f)得一long类型值,然后按前述计算此long类型的散列值

如果该域是一个对象引用,则利用此对象的hashCode,如果域的值为null,则返回0

如果该域是一个数组,则对每一个数组元素当作单独的域来处理,然后安下一步的方案来进行合成

利用下面的公式将散列码c 组合到result中。

result=37*result+c;

检查“相等的实例是否具有相等的散列码?”,如果为否,则修正错误。

  依照这个处方,得PhoneNumber的hashCode方法:

public int hashCode(){

int result=17;

result=37*result+areaCode;

result=37*result+exchange;

result=37*result+extension;

return result;

}

  如果计算散列码的代价比较高,可以考虑用内部保存这个码,在创建是生成或迟缓初始化生成它。不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。

1.hashCode()方法使用来提高Map里面的搜索效率的,Map会根据不同的hashCode()来放在不同的桶里面,Map在搜索一个对象的时候先通过hashCode()找到相应的桶,然后再根据equals()方法找到相应的对象.要正确的实现Map里面查找元素必须满足一下两个条件:

(1)当obj1.equals(obj2)为true时obj1.hashCode() == obj2.hashCode()必须为true

(2)当obj1.hashCode() != obj2.hashCode()为true时obj.equals(obj2)必须为false

2.一个类实现Comparable接口是用来决定该类对象的自然顺序,这样把该类对象放入TreeSet,TreeMap,时会根据该类对象的自然顺序自动进行排序,另外往Queue里面存的时候也会根据对象自然顺序排序

3.光实现Comparable只能决定该类对象自然顺序,但是很多时候需要自定义排序规则,那么就要实现

Comparator接口,例如一个People实现了Comparable接口并且定义自然顺序为按照名字字母顺序排序,那么把People存到List里面的时候可以用Collections.sort()方法根据自然顺序对该List进行排序,问题就在于我现在要把People按照年龄排序该怎么办呢?一个类实现了Comparable只能重写compareTo()方法一次,那么也只能有一种顺序--自然顺序,但是为了可以用age大小排序这个List的话,就必须使用一个类实现Comparator并且重写compare()方法,定义按照age排序的规则,然后调用Collections
的 static <T> void sort(List <T> list, Comparator <? super T> c) 这个方法来按照指定比较器规定的顺序来对List排序

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

public boolean equals(Object obj)

Indicates whether some other object is "equal to" this one.

The equals method implements an equivalence relation on non-null object references:

[list=5]

It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false. [/list]

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has
the value true).

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

其具体的实现代码如下所示:

Java代码


public boolean equals(Object obj) {
return (this == obj);
}

从上面的代码中可以看出,Object类的equals实现只是简单地调用了“==”运算符,也即定义中说明的,对于两个非空对象X和Y,有且仅当X和Y指向同一个对象时该函数才返回true。由于Object类是java中所有类的超类,因而其它任何类都默认继承了此函数。在大多数情况下,此默认的实现方式是合适的,因为任一个类本质上都是唯一的;但是对于那些表示“值”的类(如Integer、String等),它们引入了自己特有的“逻辑相等”的概念,此时就必须修改equals函数的实现。

例如java.lang.String类的实现如下:

Java代码


public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}

而java.lang.Integer类的实现如下:

Java代码


public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

String类的equals实现使得当两个String对象所表示的字符串的值是一样的时候就能返回true;而Integer类则当两个Integer对象所持有的整数值是一样的时候就能返回true。

因此,当我们我们自己编写的类需要引入“逻辑相等”的概念,且其超类又没有改写equals的实现时,我们就必须实现自己的equals函数。在上面Object类中关于equals函数的说明中,定义了5条改写equals函数所必须遵守的规范。其中第1条自反性(reflexivity)和第5条通常能够自行满足。下面我们重点介绍下第2点对称性(symmetric)、第3点传递性(transitive)和第4点一致性(consistent)。

对称性

即对于任意两个引用值X和Y,X.equals(Y) 和 Y.equals(X) 返回的结果必须是一致的。下面引用《effect java》上的例子来说明了违反这一特性的后果:

Java代码


public class CaseInsensitiveString {

private String s;

public boolean equals(Object obj){
if(obj instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s);
if(obj instanceof String)
return s.equalsIgnoreCase((String)obj);
return false;
}
}

然后我们定义如下的类来进行测试:

Java代码


public void test(){
CaseInsensitiveString cis = new CaseInsensitiveString("Value");
String s = "Value";
List list = new ArrayList();
list.add(cis);
System.out.println(cis.equals(s));
System.out.println(s.equals(cis));
System.out.println(list.contains(s));
}

运行test(),第一条语句的输出为true,而后两条为false。因为CaseInsensitiveString类中equals函数对String对象的比较也可能返回true,只要其大小写不敏感的字符串值相等;而在String类中对任何非String对象都返回false,这样CaseInsensitiveString的实现就违反了对称性,从而导致了第三条语句也返回了意想不到的结果false。这是因为ArrayList的contains函数实现是用实参s对list中每一个对象调用equals函数,若有equals函数返回true,则contains返回true;因而在这里contains函数必然返回false。因此,如果违反了对称性,则可能会得到意料之外的结果。解决此问题的一个办法就是在CaseInsensitiveString类的equals函数中对Stirng对象的比较无论值是否一致都返回false。

传递性

即对于任意的引用值X、Y和Z,如果 X.equals(Y) 和 Y.equals(Z) 都返回true,则 X.equals(Z) 也一定返回true。下面举例说明:

Java代码


public class Point {
private final int x;

private final int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public boolean equals(Object obj) {
if (!(obj instanceof Point))
return false;
Point p = (Point) obj;
return p.x == x && p.y == y;
}
}

public class ColorPoint extends Point {

private int z;

public ColorPoint(int x, int y, int z) {
super(x, y);
this.z = z;
}

public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) obj;
return super.equals(obj) && cp.z == this.z;
}
}

在上面的代码中定义了类Point及其子类ColorPoint,并分别改写了equals函数。下面运行如下代码来测试:

Java代码


ColorPoint cp1 = new ColorPoint(1,2,3);
ColorPoint cp2 = new ColorPoint(1,2,4);
Point p = new Point(1,2);
System.out.println(cp1.equals(p));
System.out.println(p.equals(cp2));
System.out.println(cp1.equals(cp2));

结果前两条语句输出“true”,而最后一条语句输出为“false”;从而违反了传递性规范。其主要原因是在基类的equals函数中并未对子类加以区分,从而使得基类与子类之间的比较也能返回true。要解决此方法,《effect java》上提供了“复合代替继承”的方法;另外,如果可以将基类和子类视为不等的话,使用 getClass()函数代替 instanceof 运算符进行比较可以很好的解决这一问题。getClass()会返回此对象的运行期类(runtime class),因为基类和子类属于不同的class,getClass()能够将其区分开来。下面是一段示例的代码:

Java代码


public boolean equals(Object obj) {
if(obj.getClass() != this.getClass())
return false;
Point p = (Point) obj;
return p.x == x && p.y == y;

}

一致性

即如果两个对象相等的话,那么它们必须始终保持相等,除非至少有一个对象被修改了。

在Object类equals函数的说明中的最后一段提到当我们改写equals函数的时候,通常都需要改写hashCode函数,后者同样在Object类中进行了定义,如下:

引用

public int hashCode()

Returns a hash code value for the object. This method is supported for the benefit of hashtables such as those provided by java.util.Hashtable.

The general contract of hashCode is:

[list=3]
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object
is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer
should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.[/list]

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique
is not required by the JavaTM programming language.)

hashCode()函数返回一个对象的散列值(hash code),在java中有些集合类都是基于散列值的,如HashMap、HashSet、Hashtable等;它们都根据对象的散列值将其映射到相应的散列桶中。如Hashtable的put和get函数的实现如下所示:

Java代码


public synchronized V get(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}

ublic synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}

modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();

tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}

// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<K,V>(hash, key, value, e);
count++;
return null;
}

因而hashCode函数极大地影响了这些集合类的正常工作。如果在改写完equals函数后,不相应改写hashCode函数,则可能会得不到想要的结果。如下例所示:

Java代码


Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Hashtable<Point, String> ht = new Hashtable<Point, String>();
ht.put(p1, "value");
System.out.println(p1.equals(p2));
System.out.println(ht.get(p2));

由上面Point类的实现,p1和p2是相等的,第一个语句正常输出true;但是第二个语句输出的值是null,并没有得到期望中的“value”。导致这一问题的根本原因是Point类改写了equals函数,对相等的概念作了更改,但没有相应更改hashCode函数,使得两个相等的函数拥有不同的hashCode,违反了Object类关于hashCode的约定中的第2点,从而返回了错误的结果。

在Object类中定义的几个hashCode约定如下:

1. 在同一应用中,一个对象的hashCode函数在equals函数没有更改的情况下,无论调用多少次,它都必须返回同一个整数。

2. 两个对象如果调用equals函数是相等的话,那么调用hashCode函数一定会返回相同的整数。

3. 两个对象如果调用equals函数是不相等的话,那么调用hashCode函数不要求一定返回不同的整数。

我们在改写equals 和 hashCode 函数的时候,一定要遵守如上3条约定,在改写equals的同时也改写hashCode的实现,这样才能保证得到正确的结果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: