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;
}
这个就是比较地址