java中==和equals和hashCode的区别

关于==:

如果是Java基本数据类型,==就是值的比较,比如:int a == int b;如果是对象比如 User a== User b;则比较的是对象a和b的地址;

关于equal和hashCode:

equal 是对比两个两个对象是否是等价关系。等价不同于相等。它在不同的类中有不同的规则,equal在不同的类中有不同的重载。

在Object类中,只有同一个对象才被认定为等价。

Object.class

public boolean equals(Object obj) {

    return (this == obj);

}

而在Integer类中,同为Integer类且值相等则便认为等价,而不仅仅同一个引用。

Integer.class

public boolean equals(Object obj) {

if (obj instanceof Integer) {

return value == ((Integer)obj).intValue();

}

return false; }

但无论规则是什么,等价关系必须满足定义的三大原则:

1. 自反性:对于任何非空的x,x.equals(x)都应该返回true。

2. 对称性:对于任何非空x和y,当且仅当x.equals(y)返回true时, y.equals(x)也应该返回true。

3. 传递性:对于任何非空x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。

4. 一致性:如果x和y的引用没有发生变化,多次调用x.equals(y)的结果应该相同。

5. 关于null:对于任何非空的x,x.equals(null)都应该返回false。

例如自定义Person类,当身份证identity号相等时候便可以认为两个实例等价,可以这样写:

static class Person {

public String name;

public long identity;

public Person(String name, long identity) {

this.name = name; this.identity = identity;

}

@Override

public boolean equals(Object obj) {

if (obj instanceof Person) {

if (((Person) obj).identity == this.identity) {

return true;

}

}

return false;

}

hashCode

hashCode方法返回的是对象的哈希值。

Object类的hashcode方法取决于JVM的实现,比较典型的一种实现是基于内存地址进行哈希运算,此外也有基于伪随机数的实现。

需要注意的是hashCode与equals一样,多次调用不改变返回值。所以每个对象一旦计算出其identity hash code之后,在该对象死之前都必须保持同一个identity hash code值不可以改变,而不是每次基于内存地址运算(JVM GC会影响内存地址)。

hashCode 与 equals联系

hashCode与equals方法密切相关:两个equals为true的实例必须返回相同的hashCode。这在HashMap等类的使用中是非常有用的。

套用之前的Person类,我们知道Set具有去重功能,但是如果不重写hashCode,纵然两个Person实例是等价的,也是不能达到去重的效果

public void testHash() throws Exception {

HashSet set = new HashSet<>();

Person a = new Person(“123”, 123);

Person b = new Person(“123”, 123);

set.add(a);

set.add(b);

System.out.print(“set size : ” + set.size());

} 输出 set size : 2  

下面重写hashCode方法使其与equals方法相关

@Override public int hashCode() {

return (int) identity;

} 重新run后输出 set size : 1

显然这样比较符合人的习惯,这种影响广泛存在于基于hashCode的容器类中,例如HashMap的contains方法等。

所以,一般来说重写了equals方法就需要重写hashCode方法。比如Object中equals判断的是否为相同的引用,因此hashCode基于引用内存地址返回。在Integer中判断的是Integer的value值,因此hashCode值则直接返回的是value值。

哈希碰撞

但需要注意的是,hashCode相同的两个实例,equals方法不一定会返回true。因为hashCode是一种空间映射函数,空间大的数据映射至小空间则势必会产生哈希碰撞。

举个简单的例子

String.class

public int hashCode() {

int h = hash;

if (h == 0 && count > 0) {

for (int i = 0; i < count; i++) {

h = 31 * h + charAt(i);

}

hash = h;

}

return h;

}  

上述代码为String源码,String的hashCode方法上转换成函数等同于:

hashCode = s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

简单来说就是讲其字符转换成31进制。

我们这样构造两个String实例

public void testHash() throws Exception {

char A = 1; char B = 2;

char C = 33;

String aString = String.valueOf(A) + String.valueOf(B);

String bString = String.valueOf(C);

System.out.println(“aString length: ” + aString.length());

System.out.println(“bString length: ” + bString.length());

System.out.println(“aString equals bString: ” + aString.equals(bString)); System.out.println(“aString hashCode: ” + aString.hashCode());

System.out.println(“bString hashCode: ” + bString.hashCode());

}  

输出为:

aString length: 2

bString length: 1

aString equals bString: false

aString hashCode: 33

bString hashCode: 33

显然,两个完全不一样的String对象产生可哈希碰撞。

关于数字31的引申String equals方法中选31也是为了减少哈希碰撞,引用Effective Java中的原话来说

《Effective Java》

之所以选择31,是因为它是个奇素数。

如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。

使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。

31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。

关于奇数,在计算机中,一个数乘偶数表现为该数字左移n位,余位补0,因此可能造成信息丢失。比方说,一个二进制数字X4就可能导致两位数字丢失。

1010 0110 -> 10011000

而奇数则没有这个问题,因为任何一个奇数都可以转换为2的n次方+1,表现在计算机中则是左移n位再加上自己。

所以我们同样可以选择31进制作为hashCode的一种计算方式。

关于equal 和hashCode的应用可以参考String和Integer的源码,在根据需求自定义自己对象实体类时可以借鉴。

    原文作者:蛊惑007
    原文地址: https://www.jianshu.com/p/21d71a01437e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞