Lombok 注解 @Data 引出的 equals、==、hashCode

Lombok 注解 @Data

最近工作上没啥事,一直在阅读代码,发现一个疑问,然后不断深入,竟然解锁我一个知识盲区。

是这样的,代码中有一个地方要比较两个对象,使用 equals 比较,结果两个对象比较结果竟然是 true,这很不正常,因为对象比较时,比较的是内存地址。所以就很疑惑,难道我对 ==equalshashCode 了解有误?

我将代码简化如下,对象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 被重写了。像基本数据类型 StringInteger ,这些类在内部重写 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方法

参考十分钟搞懂Lombok使用与原理

原来如此,问题就出现在@EqualsAndHashCode 上,它自动生成了 equals 方法。

可能你会觉得自动生成 equals 方法没什么,我们又不知道自动生成的方法是怎样,方法被重写成什么样。其实可以知道的,和 toStringget 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/

Lombok 注解 @Data 引出的 equals、==、hashCode》有1个想法

  1. 程序员

    也就是字符串在堆内存空间中的值,而不是这个值的地址。 这句话不对吧
    if (this == anObject) {
    return true;
    }
    这个就是比较地址

    回复

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注