Java核心(六):==和equals()的区别;重写equals()方法

时间:2021-12-22 16:23:52
一、对字符串而言,==和equals()的区别
  • "==" 比较的是两个对象的引用(内存地址)是否相同,也用来比较两个基本数据类型的变量值是否相等。
  • equals() 比较的是两个对象的值(内容)是否相同。

二、==和equals()的区别

  • 对于==:在简单类型中(int等),这能使用该方法进行比较,这种类型没有equals方法,int的值是存在栈中的,==比较的是栈的内容是否相同。在String类型中,比较特殊,用String=“****”;这种进行赋值时,两个相同的值用==比较也是相同的。但是用new String(),赋值就不相同。说明String=“”时,java会检查在堆中是否由相同的值,如果有,把新对象的地址也同老对象的地址赋为相同,因此==比较会相同(“****”存储在常量区内存中)。但是new String()开辟的就是两个栈,因此用==比较不会相同。对于包装类,如Integer num=127;时,进行自动装箱操作。如果数值在-128-127会有缓存,此时==是相同的;如果数值不在-128~127之间,则==不相同。
  • 对于equals:当时String类型或者是包装类(如Integer),比较的就是堆中的值。对于用户自定义的普通类,equals比较的内存的首地址,这时候和==是一样的,即比较两边指向的是不是同一个对象。

三、为什么重写了equals方法时,都要进而重写Hashcode方法呢?

  原因如下:当equals此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:

  (1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true

  (2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false

  hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。

  这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致。

 

四、如何重写equals()方法?

package com.study;

import java.util.Date;
import java.util.Objects;

public class Employee {

    private String name;
    private double salary;
    private Date hireDay;

    public Employee(String name, double salary, Date hireDay) {
        this.name = name;
        this.salary = salary;
        this.hireDay = hireDay;
    }

    public Employee() {
    }

    /**
     * 重写equals()方法
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        // 如果为同一对象的不同引用,则相同
        if (this == obj) {
            return true;
        }
        // 如果传入的对象为空,则返回false
        if (obj == null) {
            return false;
        }

        // 如果两者属于不同的类型,不能相等
        if (getClass() != obj.getClass()) {
            return false;
        }

        // 类型相同, 比较内容是否相同
        Employee other = (Employee) obj;
        return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
    }


}

/**
 * 测试类
 */
class Test {
    public static void main(String[] args) {
        Date date = new Date();
        Employee e1 = new Employee("zhangsan", 100, date);
        Employee e2 = new Employee("zhangsan", 100, date);
        System.out.println(e1.equals(e2));

    }
}

 

五、重写hashCode()方法

1、推荐阅读

2、如何重写hashcode方法;

  • String对象和Bigdecimal对象已经重写了hashcode方法,这些类型的值可以直接用于重写hashcode方法;
  • result = 31 *result + (dishCode !=null ?dishCode.hashCode() : 0);,这里面为啥用个31来计算,而且很多人都是这么写的,这是因为31是个神奇的数字,任何数n*31都可以被jvm优化为(n<<5)-n,移位和减法的操作效率比乘法的操作效率高很多。

3、这边再拷贝下别人说的比较经典的总结:

Google首席Java架构师Joshua Bloch在他的著作《Effective Java》中提出了一种简单通用的hashCode算法

1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;

2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:

  (1) 如果是boolean值,则计算f ? 1:0

  (2) 如果是byte\char\short\int,则计算(int)f

  (3) 如果是long值,则计算(int)(f ^ (f >>> 32))

  (4) 如果是float值,则计算Float.floatToIntBits(f)

  (5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int

  (6)如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。  否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode值为0

  (7)如果是数组,那么需要为每个元素当做单独的域来处理。如果你使用的是1.5及以上版本的JDK,那么没必要自己去    重新遍历一遍数组,java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上,


4、关于hashcode,我们一定要知道一个口诀:

  • hashcode相等,两个对象不一定相等,需要通过equals方法进一步判断;
  • hashcode不相等,两个对象一定不相等;
  • equals方法为true,则hashcode肯定一样;
  • equals方法为false,则hashcode不一定不一样;