1、equals和hashcode方法都来自Object对象。
API文档中HashCode和equals方法定义
public int hashCode()
返回该对象的哈希码值。支持此方法是为了提高哈希表性能。hashCode的常规协定是
- 在Java应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时信息没有被修改。
- 如果根据equals()方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
- 如果根据equals()方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法不要求一定生成不同的整数结果,但是程序员应该意识到,为不想等的对象生成不同的整数结果可以提高哈希表的性能。
- 实际上,由Object类定义的hashCode方法确实对针对不同的对象返回不同的整数。这一般是通过将该对象的内部地址转换成一个整数来实现的,但是Java编程语言不需要这种实现技巧。
public boolean equals()
指示其他某个对象是否与此对象“相等”。equals 方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
- 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
- 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
- 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。对于任何非空引用值 x,x.equals(null) 都应返回 false。
- Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。
- 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码
2、equals重写场景
当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)时,需要重写equals方法
3、equals和hashCode方法设计
3.1、equals方法设计
【1】.使用instanceof操作符检查“实参是否为正确的类型”。
【2】.对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的阈值。
【2.1】对于非float和double类型的原语类型域,使用==比较
【2.2】对于对象引用域,递归调用equals方法
【2.3】对于float域,使用Float.floatToIntBits(afloat)转化为int,再使用==比较。
【2.4】对于数组域,调用Arrays.equals方法.
3.2、hashCode方法设计
【1】.把某个非零常数值,保存在int变量result中
【2】.对于对象中每一个关键域f(指equals方法中考虑的每一个域):
【2.1】boolean型,计算(f?0:1)
【2.2】byte,char,short型,计算(int)
【2.3】long型,计算(int)(f ^(f >>>32))
【2.4】float型,计算Float.floatToIntBits(afloat)
【2.5】double型,计算Double.doubleToLongBits(abouble)得到一个long,再执行[2.3]
【2.6】对象引用,递归调用它的hashCode方法
【2.7】数组域,对其中每个元素调用它的hashCode方法
【3】.将上面计算得到的散列码保存到int变量c中,然后执行result=37*result +c;
【4】.返回result
4、使用示例
在Object类中定义的hashCode方法代码如下:
public native int hashCode();
这说明hashCode()本地机器相关的方法。jre6中String实现hashCode()的代码如下:
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String实现hashCode()方法中利用了公式s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]。对这公式的解读:
s[i]是string的第i个字符,n是String的长度。31为啥呢?之所以选择31,是因为它是个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不是很明显,但是习惯上都使用素数来计算散列结果。31有个很好的特性,就是用移位和减法来代替乘法,可以得到更好的性能:31*i==(i<<5)-i。现在的VM可以自动完成这种优化。
5、总结
如果两个对象调用equals(),返回结果为true,那么这两个对象一定有相同的hashCode
如果两个对象调用equals(),返回结果为false,这两个对象可以有相同的hashCode。但是这样的结果会造成散列表退化成链表,性能降低。
如果两个对象的hashCode一样,那么这两个对象一定相等。
如果对象重写了equals()方法,请程序员务必重写hashCode(),遵循hashCode协定,提高系统能。
6、重写demo
其中@Getter @Setter @ToString 是使用lombok的插件,加上这个注解就不用写get、set、toString方法了,其实这个插件也支持重写hashCode与equals,这里为了演示就没有使用这个功能,lombok可使代码简捷、提高开发效率。具体可以看下插件的官方说明。
package set.hashset;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.Arrays;
@Getter
@Setter
@ToString
public class Person {
private String name;
private Integer age;
private String address;
private double score;
private String[] tags;
public Person() {
}
public Person(String name, Integer age, String address, double score, String[] tags) {
this.name = name;
this.age = age;
this.address = address;
this.score = score;
this.tags = tags;
}
/**
* hashCode方法设计
* 【1】.把某个非零常数值,保存在int变量result中
* 【2】.对于对象中每一个关键域f(指equals方法中考虑的每一个域):
* 【2.1】boolean型,计算(f?0:1)
* 【2.2】byte,char,short型,计算(int)
* 【2.3】long型,计算(int)(f ^(f >>>32))
* 【2.4】float型,计算Float.floatToIntBits(afloat)
* 【2.5】double型,计算Double.doubleToLongBits(abouble)得到一个long,再执行[2.3]
* 【2.6】对象引用,递归调用它的hashCode方法
* 【2.7】数组域,对其中每个元素调用它的hashCode方法
* 【3】.将上面计算得到的散列码保存到int变量c中,然后执行result=37*result +c;
* 【4】.返回result
*/
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp = Double.doubleToLongBits(score);
result = (result * PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result * PRIME) + (this.getAddress() == null ? 43 : this.getAddress().hashCode());
result = (result * PRIME) + (this.getAge() == null ? 43 : this.age);
result = (result * PRIME) + (int) (temp ^ (temp >>> 32));
result = (result * PRIME) + Arrays.deepHashCode(this.tags);
return result;
}
/**
* equals方法设计
* 【1】.使用instanceof操作符检查“实参是否为正确的类型”。
*
* 【2】.对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的阈值。
*
* 【2.1】对于非float和double类型的原语类型域,使用==比较
*
* 【2.2】对于对象引用域,递归调用equals方法
*
* 【2.3】对于float域,使用Float.floatToIntBits(afloat)转化为int,再使用==比较。
*
* 【2.4】对于数组域,调用Arrays.equals方法.
*/
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person person = (Person) obj;
if (!person.canEqual((Object) this)) {
return false;
}
if (this.getName() == null ? person.getName() != null : !this.getName().equals(person.getName())) {
return false;
}
if (this.getAddress() == null ? person.getAddress() != null : !this.getAddress().equals(person.getAddress())) {
return false;
}
if (this.getAge() == null ? person.getAge() != null : !(this.getAge().intValue() == person.getAge().intValue())) {
return false;
}
if (this.getTags() == null ? person.getTags() != null : !Arrays.equals(this.tags, person.getTags())) {
return false;
}
return true;
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
}
此文章是根据 以下信息编写,增加了demo.
https://my.oschina.net/mrku/blog/737430