为什么要重写equal和==

226 阅读5分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

前言

  • 关于作者:励志不秃头的一个CURD的Java农民工,想挑战看看自己能完成多少天的更文挑战
  • 关于文章:equal和==在平时开发过程中我们用的挺频繁的,那么它们的区别是什么呢?本篇文章就浅聊下equal和==

equal和==

基本概念

  • ==比较的是值是否相等

    • 对于基本类型的变量,直接比较其存储的值是否相等
    • 对于引用类型变量,比较的是指向对象的地址是否相等 不管是基本类型还是引用类型变量,比较的都是值,只是引用类型变量存储的值是对象的地址
  • equals

    • equals()不能作用于基本数据类型的变量
    • equals()方法存在于object类种,因此所有类种的equals()都是继承于object类的
    • 在没有重写equals()方法的类中,调用equals()和==是一样的,也是比较引用类型变量的对象地址
    • 在Java提供的类中,有些类重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值。如String类

String类的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;
    }

实际测试

测试一
    public static void main(String[] args) {
        int a = 10;
        int b = 10;

        String str = "abc";
        String str1 = new String("abc");
        String str2 = new String("abc");

        System.out.println(a == b);//true
        System.out.println(str == str1);//false
        System.out.println(str == str2);//false
        System.out.println(str1 == str2);//false

        System.out.println(str.equals(str1) );//true
        System.out.println(str.equals(str2) );//true
        System.out.println(str1.equals(str2) );//true
    }
示例二

定义一个Task类

public class Task {
    private String url;
    private String detail;

    public Task(String url) {
        this.url = url;
    }
}
public static void main(String[] args) {
        Task task1 = new Task("www.xx.com");
        Task task2 = new Task("www.xx.com");
        System.out.println(task1 == task2);//false
        System.out.println(task1.equals(task2));//false
    }

此时equals方法没有重写,调用的是基类object的equals方法,也是就==比较,所以打印出来是false

重写equals()

public class Task {
    private String url;
    private String detail;

    public Task(String url) {
        this.url = url;
    }

    //重写equals,定义url相同便返回ture
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Task task = (Task) obj;
        return Objects.equals(url, task.url);
    }
}
public static void main(String[] args) {
        Task task1 = new Task("www.xx.com");
        Task task2 = new Task("www.xx.com");
        System.out.println(task1 == task2);//false
        System.out.println(task1.equals(task2));//ture
    }

编写高质量的 equals 和 hashcode 方法

为什么需要重写 equals 方法和 hashcode 方法

  1. Object 中的 equals 方法是用来判断两个对象的引用是否相同,但是有时候我们并不需要判断两个对象的引用是否相等,我们只需要两个对象的某个特定状态是否相等。比如对于两条任务来说,我只要判断两条任务的链接是否相同,如果链接相同,那么它们就是同一条任务,并不需要去比较其它属性或者引用地址是否相同。
  2. 在某些业务场景下,我们需要使用自定义类作为哈希表的键,这时候我们就需要重写,因为如果不做特定修改的话,每个对象产生的 hashcode 基本上不可能相同,而 hashcode 决定了该元素在哈希表中的位置,equals 决定了判断逻辑,所以特殊情况下就需要重写这两个方法,才能符合我们的要求。
示例

有两条任务,需要判断任务是否是Set中,两条任务的链接是相同的 Task类

public class Task {
    private String url;
    private String detail;

    public Task(String url) {
        this.url = url;
    }
}

测试类

  public static void main(String[] args) {
        Task task1 = new Task("www.xx.com");
        Task task2 = new Task("www.xx.com");
        System.out.println(task1 == task2);//false
        System.out.println(task1.equals(task2));//false

        Set<Task> set = new HashSet<>();
        set.add(task1);
        System.out.println(set.contains(task2));//false
    }

在测试类中,实例化了两个task对象,url都保持一样,同时将对象存入到Set中,打印对应的结果,发现结果都为false,原因很简单,这时候的equals都是Object类的equals方法

重写 equals 方法

    //重写equals,定义url相同便返回ture
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Task task = (Task) obj;
        return Objects.equals(url, task.url);
    }

测试类

 public static void main(String[] args) {
        Task task1 = new Task("www.xx.com");
        Task task2 = new Task("www.xx.com");
        System.out.println(task1 == task2);//false
        System.out.println(task1.equals(task2));//true

        Set<Task> set = new HashSet<>();
        set.add(task1);
        System.out.println(set.contains(task2));//false
    }

这时候重写了equals方法,但是set.contains(task2)还是为false,这是因为他们的hashcode不同导致的

重写hashcode方法

@Override
    public int hashCode() {
        return Objects.hashCode(url);
    }

如何编写 equals 和 hashcode 方法

equals方法

重写 equals 方法时,需要重写 equals 方法的通用约定

  • 自反性:对于任何非空引用 x,x.equals(x) 必须返回 true
  • 对称性:对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true
  • 传递性:对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 必须返回 true
  • 一致性:对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则 x.equals(y) 的多次调用必须始终返回 true 或始终返回 false
  • 非空性:对于任何非空引用 x,x.equals(null) 必须返回 false
    //重写equals,定义url相同便返回ture
    @Override
    public boolean equals(Object obj) {
        //判断是否等于自身
        if (this == obj) return true;
        //判断obj对象是否为null或者类型是否正确
        if (obj == null || getClass() != obj.getClass()) return false;
        //类型转换
        Task task = (Task) obj;
        //判断两个对象的url是否相同
        return Objects.equals(url, task.url);
    }

在 effective-java 书中总结了一套编写高质量 equals 方法的配方,配方如下:

  1. 使用 == 运算符检查参数是否为该对象的引用。如果是,返回 true。
  2. 使用 instanceof 运算符来检查参数是否具有正确的类型。 如果不是,则返回 false。
  3. 参数转换为正确的类型。因为转换操作在 instanceof 中已经处理过,所以它肯定会成功。
  4. 对于类中的每个「重要」的属性,请检查该参数属性是否与该对象对应的属性相匹配。

总结

  • 当重写 equals 方法时,同时也要重写 hashCode 方法
  • 不要让 equals 方法试图太聪明。
  • 在 equal 时方法声明中,不要将参数 Object 替换成其他类型。

想要让自己的Object作为K应该怎么办呢? (面试问过)

重写hashCode()和equals()方法

  • 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
  • 重写equals()法目的是为了保证key在哈希表中的唯一性,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性;

好了,以上就是本篇文章的全部内容,equal和==还是需要多多了解的。我是新生代农民工L_Denny,我们下篇文章见。