Java equals and hashCode of these problems can be said to understand?

Foreword

An article on how magical Spring data binding? , Soul questioning aspects of leaving a question about equals and hashcode. The basis of the interview often encounter the problem associated with this is not a complex issue, but many of my friends suffer from the relationship and bind them both, then do a separate description of this writing, this article will be gradual ( by way of example, let memory and understanding easier ) illustrate these problems make you some distress, let's go .......

Interview Questions

1. Java == operator which has, why do you need equals?

==Compare that object address, equalsthe comparison is the target value

First look at Objectthe class equalsmethod:

public boolean equals(Object obj) {
    return (this == obj);
}

We see equalsthe same way and by ==comparison address, did not help us compare value. Java world Objectis definitely the existence of "ancestors", the ==number we can not change or override. But it equalsis a method, which gives us rewrite equalspossible method, let us realize that the comparison of the value of:

@Override
public boolean equals(Object obj) {
    //重写逻辑
}

Buy a new computer, each computer has a unique serial number, usually, two identical computer in front of you will say because the serial numbers are different, the two computer does not like it?

If we want to say two computers, as usually compare their "brand / size / configuration" (value), like this:

@Override
public boolean equals(Object obj) {
    return 品牌相等 && 尺寸相等 && 配置相等
}

When confronted with the above scenario, we need to override the equalsmethod. This explains why the Java world have ==there is equalsthis problem.

2. equalsequal and hashcodeequal problem

About two, you often encounter the following two questions:

  • Two objects equalsare equal, that they hashCodeare equal it?
  • Two objects hashCodeare equal, that they equalsare equal it?

To illustrate the conclusions of the above two issues here give a very appropriate example, only easy to remember , we will equalscompare the spelling of a word; hashCodecompared to the pronunciation of a word, in the same context:

sea / sea, "the sea", as spelled two words, so equalsequal, they pronunciation /siː/is the same, so hashCodeit is equal, which answer to the first question:

Two objects equalsare equal, then they hashCodemust have equal

sea / see "the sea / see" two word pronunciation /siː/as the word is obviously not the same, which answers the second question:

Two objects hashCodeare equal, that they equalsare not necessarily equal

View Objectclass hashCodemethods:

public native int hashCode();

View Comments continue this approach, clearly stated on the method of restraint

In fact, behind this result, there is about to rewrite equalsconstraint method

3. Rewrite equalswhich constraints have?

About rewrite equalsconstraint method, also in the notes of the method written very clearly, and I am here to elaborate on:

Orange red green blue violet, colorful Israel; Duolaimi hair instigate Rasi, an Angolan , these rules are not used to recite, but you need to be rewritten equalswhen the method, see the JDK open method, in accordance with guidelines to rewrite it is good

4. When should we rewrite hashCode?

为了比较值,我们重写 equals 方法,那什么时候又需要重写 hashCode 方法呢?

通常只要我们重写 equals 方法就要重写 hashCode 方法

为什么会有这样的约束呢?按照上面讲的原则,两个对象 equals 相等,那他们的 hashCode 一定也相等。如果我们只重写 equals 方法而不重写 hashCode 方法,看看会发生什么,举个例子来看:

定义学生类,并通过 IDE 只帮我们生成 equals 方法:

public class Student {

    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;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }
}

编写测试代码:

Student student1 = new Student();
student1.setName("日拱一兵");
student1.setAge(18);

Student student2 = new Student();
student2.setName("日拱一兵");
student2.setAge(18);

System.out.println("student1.equals(student2)的结果是:" + student1.equals(student2));

Set<Student> students = new HashSet<Student>();
students.add(student1);
students.add(student2);
System.out.println("Student Set 集合长度是:" + students.size());

Map<Student, java.lang.String> map = new HashMap<Student, java.lang.String>();
map.put(student1, "student1");
map.put(student2, "student2");
System.out.println("Student Map 集合长度是:" + map.keySet().size());

查看运行结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:2
Student Map 集合长度是:2

很显然,按照集合 Set 和 Map 加入元素的标准来看,student1 和 student2 是两个对象,因为在调用他们的 put (Set add 方法的背后也是 HashMap 的 put)方法时, 会先判断 hash 值是否相等,这个小伙伴们打开 JDK 自行查看吧

所以我们继续重写 Student 类的 hashCode 方法:

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

重新运行上面的测试,查看结果:

student1.equals(student2)的结果是:true
Student Set 集合长度是:1
Student Map 集合长度是:1

得到我们预期的结果,这也就是为什么通常我们重写 equals 方法为什么最好也重写 hashCode 方法的原因

  • 如果你在使用 Lombok,不知道你是否注意到 Lombok 只有一个 @EqualsAndHashCode 注解,而没有拆分成 @Equals 和 @HashCode 两个注解,想了解更多 Lombok 的内容,也可以查看我之前写的文章 Lomok 使用详解

  • 另外通过 IDE 快捷键生成重写方法时,你也会看到这两个方法放在一起,而不是像 getter 和 setter 那样分开

以上两点都是隐形的规范约束,希望大家也严格遵守这个规范,以防带来不必要的麻烦,记忆的方式有多样,如果记不住这个文字约束,脑海中记住上面的图你也就懂了

5. 重写 hashCode 为什么总有 31 这个数字?

细心的朋友可能注意到,我上面重写 hashCode的方法很简答, 就是用了 Objects.hash 方法,进去查看里面的方法:

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

这里通过 31 来计算对象 hash 值

如何妙用 Spring 数据绑定? 文章末尾提到的在 HandlerMethodArgumentResolverComposite 类中有这样一个成员变量:

private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
            new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);

Map 的 key 是 MethodParameter ,根据我们上面的分析,这个类一定也会重写 equalshashCode 方法,进去查看发现,hashCode 的计算也用到了 31 这个数字

@Override
public boolean equals(Object other) {
    if (this == other) {
        return true;
    }
    if (!(other instanceof MethodParameter)) {
        return false;
    }
    MethodParameter otherParam = (MethodParameter) other;
    return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember()));
}

@Override
public int hashCode() {
    return (getMember().hashCode() * 31 + this.parameterIndex);
}

为什么计算 hash 值要用到 31 这个数字呢?我在网上看到一篇不错的文章,分享给大家,作为科普,可以简单查看一下:
String hashCode 方法为什么选择数字31作为乘子

总结

如果还对equalshashCode 关系及约束含混,我们只需要按照上述步骤逐步回忆即可,更好的是直接查看 JDK 源码;另外拿出实际的例子来反推验证是非常好的办法。如果你还有相关疑问,也可以留言探讨.

灵魂追问

  1. Thread 类就没有重写 equals 方法,你还知道哪些情况没必要重写 equals 方法吗?
  2. 从上面 HandlerMethodArgumentResolverComposite 类中定义的 Map 成员变量,你注意到哪些知识点,比如 final,ConcurrentHashMap,初识容量,为什么要这样写?你能解释出原因吗?

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


Guess you like

Origin www.cnblogs.com/FraserYu/p/12081976.html
Recommended