Java equals方法详解

495 阅读6分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战


Object中的equals()

object中的的equals方法用于比较两个对象是否相等,该方法源码如下:

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

对象均有内存地址和和其具体内容,而object中的equals方法是比较的两个对象内存地址是否相同,即obj1.equals(obj2)为true,这表示两者是引用同一个对象,但在实际开发中,大部分是两个对象之间的比较,此时再用object的equals方法就不行了,因此就要根据自己的需求重写equals方法,java中的String类,Math类等均对equals方法进行了重写

equals方法重写遵循规则

  1. 自反性 :对于任何非空引用值 x,x.equals(x) 都应返回 true。
  2. 对称性 : 对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
  3. 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
  4. 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  5. 对于任何非空引用值 x,x.equals(null) 都应返回 false。

重写equals方法实现

  1. 创建一个ObjectTest1类
/**
 * Created by lirui on 2018/12/12.
 */
public class ObjectTest1 {
    private  Integer size;
    private String name;
    private String des;

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }
    }
  1. 创建测试类
public class EqualsDemo {
    public static void main(String[] args) {
      ObjectTest1 objectTest1=new ObjectTest1();
     objectTest1.setName("wahh");
      ObjectTest1 objectTest12=new ObjectTest1();
        objectTest12.setName("wahh");
        System.out.println("-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=");
        System.out.println(objectTest1.equals(objectTest12));
    }
}
-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=
false

Process finished with exit code 0

可以看到运行结果为false,因为没有重写equals方法,所以直接就会用Object类中的equals方法,比较两者内存地址,因为是两个对象,所以内存地址不一样

  1. 重写equals方法
/**
 * Created by lirui on 2018/12/12.
 */
public class ObjectTest1 {
    private  Integer size;
    private String name;
    private String des;

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    @Override
    public boolean equals(Object obj) {
        if (this==obj){
            return true;
        }
        if (obj  instanceof ObjectTest1){
            ObjectTest1 objectTest1=(ObjectTest1) obj;
            return name.equals(objectTest1.name);
        }else{
            return false;
        }
    }
    }
  1. 测试类结果
/**
 * Created by lirui on 2018/12/12.
 */
public class EqualsDemo {
    public static void main(String[] args) {
      ObjectTest1 objectTest1=new ObjectTest1();
     objectTest1.setName("wahh");
      ObjectTest1 objectTest12=new ObjectTest1();
        objectTest12.setName("wahh");
        System.out.println("-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=");
        System.out.println(objectTest1.equals(objectTest12));
    }
}
-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=
true

可以看到最后返回true,重写equals方法首先判读两个对象内存地址是否相同,然后根据instanceof关键字判断左边对象是不是右边的一个实例,如果是,最后判断两者name是否相同

  1. 在equals()中使用getClass进行类型判断
    创建 ObjectTestChild类继承ObjectTest1
/**
 * Created by lirui on 2018/12/13.
 */
public class ObjectTestChild extends ObjectTest1{
    String test;
    public ObjectTestChild(String name,String test) {
        super(name);
        this.test=test;
    }
    public String getTest() {
        return test;
    }
    public void setTest(String test) {
        this.test = test;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj==this)
        return true;
        if (obj instanceof ObjectTestChild){
            ObjectTestChild objectTestChild=(ObjectTestChild) obj;
            return super.equals(objectTestChild)&&objectTestChild.test==test;
        }
        return false;
    }
}

上面的子类和父类都重写了equals方法,子类只比父类多了一个test属性,测试程序如下

public class EqualsDemo {
    public static void main(String[] args) {
        ObjectTest1 objectTest1 = new ObjectTest1();
        objectTest1.setName("wahaha");
        ObjectTestChild child1 = new ObjectTestChild("wahaha", "123");
        ObjectTestChild child2 = new ObjectTestChild("wahaha", "456");
        System.out.println("-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=");
        System.out.println(objectTest1.equals(child1));
        System.out.println(objectTest1.equals(child2));
        System.out.println(child1.equals(child2));
    }
}
-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=
true
true
false

上面定义了两个子类和一个父类,两个子类因为test不同,所以比较后为false是正常的,但服了分别和两个子类比较均为true,这就和equals的传递性相违背;之所以会存在这个现象是因为我们使用了instanceof 关键字进行比较,因为子类继承父类,所以左边肯定等于右边的实例,于此同时两者name又相等,所以最好比较后相等,故在覆写equals时推荐使用getClass进行类型判断。而不是使用instanceof,getClass方法是返回运行时的类
改造父类和子类的equals重写方法

父类
    @Override
    public boolean equals(Object obj) {
        if (this==obj){
            return true;
        }
        if (obj.getClass()==this.getClass()){
            ObjectTest1 objectTest1=(ObjectTest1) obj;
            return name.equals(objectTest1.name);
        }else{
            return false;
        }
    }
    子类
     public boolean equals(Object obj) {
        if (obj==this)
        return true;
        if (obj.getClass()==this.getClass()){
            ObjectTestChild objectTestChild=(ObjectTestChild) obj;
            return super.equals(objectTestChild)&&objectTestChild.test==test;
        }
        return false;
    }

测试程序

public class EqualsDemo {
    public static void main(String[] args) {
        ObjectTest1 objectTest1 = new ObjectTest1();
        objectTest1.setName("wahaha");
        ObjectTestChild child1 = new ObjectTestChild("wahaha", "123");
        ObjectTestChild child2 = new ObjectTestChild("wahaha", "456");
        System.out.println("-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=");
        System.out.println(objectTest1.equals(child1));
        System.out.println(objectTest1.equals(child2));
        System.out.println(child1.equals(child2));
    }
}
-=-=-=--=-=-=-=-=运行结果-=-=-=-=-=-=-=-=
false
false
false

重写equals方法时为何要重写hashCode方法

首先,equals方法与hashCode有如下的关系

  1. 如果两个对象相同(即equals比较返回true),那么两者hashCode相同
  2. 如果两个对象hashCode比较相同,那么它们并不一定相同(即equals比较返回false)

为何重写hashCode方法

由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对需要比较的数量很大时效率提高是很明显的,一个很好的例子就是在集合中的使用;

我们都知道java中的List集合是有序的,因此是可以重复的,而set集合是无序的,因此是不能重复的,那么怎么能保证不能被放入重复的元素呢,单靠equals方法一样比较的话,如果原来集合中有10000个元素了,那么放入第10001个元素,难道要将前面的所有元素都进行比较,看看是否有重复,这个效率可想而知,因此hashcode就应运而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,如果有值那么就用equals比较,如果没有则直接插入,这样就大大减少了equals的使用次数,执行效率就大大提高了。

为什么必须要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的情况下hashcode值必定相同,如果重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(因为equal都是根据对象的特征进行重写的),但hashcode确是不相同的。