Lombok 注解 @Data
最近工作上没啥事,一直在阅读代码,发现一个疑问,然后不断深入,竟然解锁我一个知识盲区。
是这样的,代码中有一个地方要比较两个对象,使用 equals 比较,结果两个对象比较结果竟然是 true,这很不正常,因为对象比较时,比较的是内存地址。所以就很疑惑,难道我对 ==、equals、hashCode 了解有误?
我将代码简化如下,对象User
@Data
public class User {
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
创建两个User对象,再比较
public static void main(String[] args) {
User user1 = new User("1", "haier");
User user2 = new User("1", "haier");
System.out.println(user1 == user2);
System.out.println(user1.equals(user2));
}
现在开始猜,结果是什么?
false
true
发现问题没? equals比较返回结果竟然是true。这很不正常,因为equals 是超类 Object的方法。如果子类不重写,默认是比较内存地址。
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
equals、==、hashCode
再继续之前,我觉得要对上面三做个简单介绍,因为它们经常被搞混,面试经常被问到,经常被用到。
前面我提到,equals()是Object的方法,但如果看方法内部,其实还是用(this == obj) 比较。
所以默认 equals 和 == 是一样的,都是比较两个对象内存地址是否相等,当然,其实是堆内存的地址。
这个堆内存地址是由于 new 对象时在内存开辟空间
但刚才说的是默认情况,很多类中,这个 equals 被重写了。像基本数据类型 String、Integer ,这些类在内部重写 equals 方法。
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;
}
看上面代码可以发现,其实对于 String 类,它的 equals 是比较字符串的内容,也就是字符串在堆内存空间中的值,而不是这个值的地址。
所以,如果 new 两个一样的字符串,用 == 比较,还是 false ,因为地址不一样,new 会重新开辟堆内存空间。但 equals 比较会返回 true ,因为两者字符串的值是一样的。
再来看 hashCode ,用上面的对象运行执行这行代码
user1.hashCode();
再进入这个方法内部就能看到 hashCode 的真面目,
public native int hashCode();
对,你没看错,它和 equals 都是超类 Object 的方法,而且都能被重写。它的作用是将对象在内存中的地址作为哈希码返回,这是一个int值。
从上面那句话可以得出一个结论,如果两个对象 equals 是相等的,那它们的哈希值也是一样的,因为这个哈希值是由同一个地址生成的。哈希值有生成公式,但只要公式中不涉及随机性,那同一个地址生成的值,肯定是一样的。
给大家看看String类中哈希值的生成方法和公式,和 equals 一样,它也重写了hashCode 。
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;
}
可能看不懂这方法里计算公式,但没关系,方法上面有解释。大致可以看明白,这个值与字符串本身有关系,没随机性。
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
ok,再回头看第一部分例子就显得很奇怪。
User user1 = new User("1", "haier");
User user2 = new User("1", "haier");
System.out.println(user1 == user2);
System.out.println(user1.equals(user2));
我也“没”重写 equals ,按道理 equals 和 == 是一样的,都是比较内存地址。按道理 equals 也应该返回false ,但运行结果是 true。
问题就出现在定义类的注解@Data ,往上翻可以看到我在定义类时用到Lombok的注解。这是一个经常被用到的注解,平时也就用。
使用@Data相当于使用了@ToString、@EqualsAndHashCode、@Getter、@Setter、@RequiredArgsConstrutor
再继续看@EqualsAndHashCode,因为只有它有可能与产生的问题有关。
@EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法
原来如此,问题就出现在@EqualsAndHashCode 上,它自动生成了 equals 方法。
可能你会觉得自动生成 equals 方法没什么,我们又不知道自动生成的方法是怎样,方法被重写成什么样。其实可以知道的,和 toString 、get set 一样,每个类创建后,我们开发工具都有自动提供一些快速生成方法的功能。

只不过,有些我们不常用到,比如这里的equals and hashCode 。使用它后会根据类中属性,生成两个方法,我们看看equals 被重写成啥样。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
User user = (User) o;
if (id != null ? !id.equals(user.id) : user.id != null) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
原来重写后,两个类equals比较,被重写成两个类中属性的比较,比较两个对象的属性值是否相等。
我举例中的两个对象,属性值是一样的,所以equals 自然是相等的,返回 true 是对的。
ok,完结。
本文由老郭种树原创,转载请注明:https://guozh.net/lombok-data-equals-hashcode/
也就是字符串在堆内存空间中的值,而不是这个值的地址。 这句话不对吧
if (this == anObject) {
return true;
}
这个就是比较地址