Java equals 和 == 完全解析

3次阅读

共计 4608 个字符,预计需要花费 12 分钟才能阅读完成。

今天朋友突然问到一个问题:
两个对象使用 x.equals(y) 判断结果为 true 时,两个对象的 hashCode 可以不同吗?
在 Java 编程中,判断两个对象是否相等常常使用 equals() 或是 ==,但是其中的区别和原理可能很多人并不完全清楚。今天就借着上面这个问题来看看 equals() 和 == 的区别和原理。
1. 数据类型与 == 的含义
Java 中的数据类型分为基本数据类型和引用数据类型:

基本类型:编程语言中内置的最小粒度的数据类型。它包括四大类八种类型

4 种整数类型:byte、short、int、long
2 种浮点数类型:float、double
1 种字符类型:char
1 种布尔类型:boolean

引用类型:引用也叫句柄,引用类型,是编程语言中定义的在句柄中存放着实际内容所在地址的地址值的一种数据形式


接口
数组

对于基本类型来说,== 比较的是它们的值
对于引用类型来说,== 比较的是它们在内存中存放的地址(堆内存地址)

例:
public void test(){
int num1 = 100;
int num2 = 100;

String str1 = “James”;
String str2 = “James”;

String str3 = new String(“James”);
String str4 = new String(“James”);

System.out.println(“num1 == num2 : ” + (num1 == num2));
System.out.println(“str1 address : ” + System.identityHashCode(str1) + “;\nstr2 address : ” + System.identityHashCode(str1) + “;\nstr1 == str2 : ” + (str1 == str2));
System.out.println(“str3 address : ” + System.identityHashCode(str3) + “;\nstr4 address : ” + System.identityHashCode(str4) + “;\nstr3 == str4 : ” + (str3 == str4));
}
运行上面的代码,可以得到以下结果:
num1 == num2 : true

str1 address : 1174290147;
str2 address : 1174290147;
str1 == str2 : true

str3 address : 1289696681;
str4 address : 1285044316;
str3 == str4 : false
可以看到 str1 和 str2 的内存地址都是 1174290147, 所以使用 == 判断为 true, 但是 str3 和 str4 的地址是不同的,所以判断为 false。
2. equals() 方法解析
在 Java 语言中,所有类都是继承于 Object 这个超类的,在这个类中也有一个 equals() 方法,那么我们先来看一下这个方法。
public boolean equals(Object obj) {
return (this == obj);
}
可以看得出,这个方法很简单,就是比较对象的内存地址的。所以在对象没有重写这个方法时,默认使用此方法,即比较对象的内存地址值。但是类似于 String、Integer 等类均已重写了 equals()。下面以 String 为例。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = length();
if (n == anotherString.length()) {
int i = 0;
while (n– != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}
很明显,String 的 equals() 方法仅仅是对比它的 数据值,而不是对象的 内存地址。
以 String 为例测试一下。
public void test() {

String str1 = “James”;
String str2 = “James”;

String str3 = new String(“James”);
String str4 = new String(“James”);

System.out.println(“str1 address : ” + System.identityHashCode(str1) + “;\nstr2 address : ” + System.identityHashCode(str1) + “;\nstr1.equals(str2) : ” + str1.equals(str2));
System.out.println(“str3 address : ” + System.identityHashCode(str3) + “;\nstr4 address : ” + System.identityHashCode(str4) + “;\nstr3.equals(str4) : ” + str3.equals(str4));
}
结果为:
str1 address : 1174290147;
str2 address : 1174290147;
str1.equals(str2) : true

str3 address : 1289696681;
str4 address : 1285044316;
str3.equals(str4) : true
可以发现不管对象的内存地址是否相同并不影响其结果,所以 String 类型比较的是 数据值, 而不是 内存地址值。
所以总结一下 equals() 和 == 的区别:

==

基本类型:对比它们的值是否相等
引用类型:对比它们的内存地址是否相等

equals()

基本类型:使用 == 进行比较
引用类型:默认情况下,对比它们的地址是否相等;如果 equals() 方法被重写,则根据重写的要求来比较。

3. equals() 与 hashCode()
在详细的了解了 == 和 equals() 的作用和区别后,现在来研究一下之前的那个问题:
两个对象使用 x.equals(y) 判断结果为 true 时,两个对象的 hashCode 可以不同吗?
首先我们需要知道 hashCode 到底是什么?还是从 Object 这个超类来看一下。
public int hashCode() {
return identityHashCode(this); // 此处返回对象的内存地址值
}
代码也很简单,看来默认情况下,hashCode 就等于对象的 内存地址值(注:System.identityHashCode(Object obj) 方法用于获取对象的内存地址,之前的样例代码中有使用)。和 equals() 方法一样重写之后,hashCode() 方法方法也是可以被重写的,而且两者一般情况下都是成对出现。
简单测试一下 String 类型重写 hashCode() 方法之后有什么变化。
public void test() {
String str1 = “James”;
System.out.println(“str1 address : ” + System.identityHashCode(str1) + “\nstr1 hashCode : ” + str1.hashCode());
}
结果为:
str1 address : 1174290147
str1 hashCode : 71338276
很明显,hashCode 已经不是内存地址了。
那么总结一下:

equals():默认情况下比较的是对象的 内存地址值,被重写后按照重写要求进行比较,一般是比较对象的 数据值

hashCode(): 默认情况下为对象的 内存地址值,被重写后按照重写要求生成新的值。

到此对于刚开始提出的问题应该很好解决了。对于这两个对象,只要我们重写 equals() 方法,就可以比较对象的 数据值,而不重写 hashCode() 方法,此时两个对象的 hashCode 就默认为内存地址值了,只要将两个对象指向不同的地址即可。
验证环节,先创建一个类:
public class CustomBean {
private String name;
private int age;

public CustomBean(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomBean that = (CustomBean) o;
return age == that.age &&
Objects.equals(name, that.name);
}

// @Override
// public int hashCode() {
// return Objects.hash(name, age);
// }
}
创建测试方法:
@Test
public void test() {
CustomBean x = new CustomBean(“James”, 18);
CustomBean y = new CustomBean(“James”, 18);

System.out.println(“x.hashCode: ” + x.hashCode());
System.out.println(“x address : ” + System.identityHashCode(x));
System.out.println(“y.hashCode: ” + y.hashCode());
System.out.println(“x address : ” + System.identityHashCode(y));

System.out.println(“x and y is equals : ” + x.equals(y));
}
运行结果为:
x.hashCode: 1174290147
x address : 1174290147

y.hashCode: 1289696681
x address : 1289696681

x and y is equals : true
很明显,这就是问题中所描述的那种情况:两个对象使用 x.equals(y) 判断结果为 true 时,两个对象的 hashCode 不相同。
4. 总结
至此,== 和 equals() 的区别及作用,equals() 和 hashCode 的关系及使用已经了解清楚了。下面再总结一下:
对于 equals() 和 == 的区别:

==

基本类型:对比它们的值是否相等
引用类型:对比它们的内存地址是否相等

equals()

基本类型:使用 == 进行比较
引用类型:默认情况下,对比它们的地址是否相等;如果 equals() 方法被重写,则根据重写的要求来比较

对于 equals() 和 hashCode() 的关系:
根据 Object 超类中的文档说明,equals() 和 hashCode() 两个方法应该 同进同退。上面的例子只是举例说明存在那种情况,但那并不是一个很好的应用。

所以一定要记住 equals() 和 hashCode() 两个方法应该 同进同退。
所以一定要记住 equals() 和 hashCode() 两个方法应该 同进同退。
所以一定要记住 equals() 和 hashCode() 两个方法应该 同进同退。

重要的事情说三遍。
欢迎您关注我的博客主页:James Blog

正文完
 0