细说 equals 和==的那些事
1,起源于 Object
我们都知道 Java 中所有的类都默认继承了 Object 这个超类,其中 Object 有 10 多个方法,这里我们介绍一下主要的toString
,equals
和hashcode
方法。
toSting
方法默认返回“类名@地址值
”;我们一般对它进行重写,然后就可直接打印对象得到输出重写后的形式。
下面重点介绍一下equals
和hashcode
:
equals
方法默认比较对象(地址),即进行的是==运算。如果需要比较内容,需要进行重写。hashcode
默认是根据对象地址返回一串整数,也就是说地址相同hashcode
就相同。而且hashcode
是 Object 中的native
方法,本地方法,表明该方法事调用了别的语言实现的,有点像接口,不用自己实现。
下面是 Object 类中的相关源码:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
2,纠缠在 String
我们再来说一下java.lang.String
中equals
和hashcode
的特点:
String 对equals
和hashcode
都进行了重写。
首先 hashcode 是根据对象内容计算 hash,因此对象内容相同,他们的 hashcode 就相同。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
//这种 hash 计算方式不难看出,元素 value 相同 h 会是一样的
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
对于 string 而言,equlas 比较的是对象内容,这也是由于字符串的特殊性。
那么==比较的就是对象地址。
public boolean equals(Object anObject) {
//对象相同,直接相同
if (this == anObject) {
return true;
}
//类型相同且每个元素值相同,认为相同
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3,代码验证
下面是测试代码:
public class TEST {
public static void main(String[] args){
Object o1 = new Object();
Object o2 = new Object();
Object o3 = o2;
System.out.println(o1==o2); //false
System.out.println(o1.equals(o2)); //false,Object 原生 equals,默认比较地址
System.out.println(o1.hashCode()==o2.hashCode()); //false,根据地址计算 hash
System.out.println(o2.hashCode()==o3.hashCode()); //true
String s1 = "abc";
String s2 = s1; //常量池优化机制
String s3 = new String("abc");
System.out.println(s1==s3); //false 地址不一样,一个在常量池中一个在堆中
System.out.println(s1.equals(s3)); //true,比较内容
System.out.println(s2.hashCode()==s1.hashCode()); //true,根据内容计算 hash
System.out.println(s1.hashCode()==s3.hashCode()); //true
}
}
4,重写方法
有些场景中,我们需要自定义”相等“规则。比如我们定义对象的内容相同即为相等,此时需要重写 equals 方法:
import java.util.Objects;
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
// 如果对象地址一样,则认为相同
if (this == o)
return true;
// 如果参数为空,或者类型信息不一样,则认为不同
if (o == null || getClass() != o.getClass())
return false;
// 转换为当前类型
Person person = (Person) o;
// 要求基本类型相等,并且将引用类型交给 java.util.Objects 类的 equals 静态方法取用结果
return age == person.age && Objects.equals(name, person.name);
}
}
//对象比较地址,字符串比较内容
public static boolean equals(Object a, Object b) {
//1,对象类型时
//a==b 为 true,则 true
//a==b 为 false,则 false;
//2,string 类型时
//地址 true,内容 true,则 true
//地址 false,内容 true,则 true;地址不等内容不等,flase
return (a == b) || (a != null && a.equals(b));
}
在有些场合,我们还需要重写 hashcode,比如 HashSet 中“元素相等”的判断机制就是综合考虑了 hashcode 和 equals 方法;所以当我们自定义对象时需要重写 hashcode 和 equals 两个方法。
equals 的方法重写参考上例,下面是 hashcode 方法的重写以及相应方法源码:
@Override
public int hashCode() {
//仍然调用的是 Objects 的 hash 方法
//对于这里而言,age 走地址判断;name 走内容判断;所以只要他们内容相同就会返回相同的 hash
return Objects.hash(name, age);
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
//仍然走的是 Object 的 hashCode 方法
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
当然上述重写方法,idea 的 Generate 提供了重写模板,可一键生成。
5,总结
Object 的 hashcode 根据地址返回值,equals 比较地址,==比较地址;
String 的 hashcode 根据内容返回值,equals 比较内容,==比较地址。
即 Object 一律比较的是地址,由于 String 进行了方法重写,都是比较的内容。==其实本身就是比较地址。