第八条 覆盖equals请遵守通用的约定(一)

来源:互联网 发布:照片电子书制作软件 编辑:程序博客网 时间:2024/04/28 11:32
第八条 覆盖equals请遵守通用的约定(一)

对象的equsls()方法经常会用到,没用过的都不好意思说自己是做java的。那么,这个方法怎么用,或者有什么用处,下面慢慢讲解。
类被实例化出来对象,对象本质上都是唯一的,如果不重写equals()方法,那么,每个类都只与它自身相等。以下四种情况适用这个形式:
一、类的每个实例本质上是唯一的,一般可用 == 来比较,此时比较的是在内存中的地址值,如果两个对象指向一个地址值,那么就认为这两个实例对象是同一个。
二、不关心是否提供了逻辑相等的功能。比如一些工具类,例如随机数的Random,我们使用时是要返回一个随机数,基本用不到去比较两个Random是否是同一个对象。
三、父类已经覆盖了equals()方法,子类不用重写就够用了,例如 List实现从AbstractList类继承了equals,使用equals()方法时,直接调用父类的就够用了。
四、如果类或者包是私有的,那么它的equals方法不会被调用,此时,为了防止子类错误的使用equals方法,或者对象调用此方法,应该覆盖此方法,在方法内部抛出一个异常,加以说明。
以上是创建对象或使用对象,不考虑equals()方法的场景,下面说说使用的原则和什么时候使用。

equals()方法使用时有几个原则,自反性、对称性、传递性、一致性、非空性。
对任何一个非null的对象x和y及z,x.equals(x)必须返回true,x.equals(y) 和 y.equals(x) 的值必须相等,x.equals(null)必须返回false;如果x.equals(y)为true,y.equals(z)为true,那么

x.equals(z)也必须为true。

这么说可能有点笼统,那么简单的举个例子。
public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
班级里面有几十个学生,比较这些学生的信息,如果名字和年龄一致,就说明是同一个人,如果不一致,就是不同的人。(假如班级没有重名的)
此时,
Student s1 = new Student("jim",12);
Student s2 = new Student("jim",12);
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
返回两个false,因为 == 比较的是地址值,只要不是单利模式或枚举类型,每一个new出来的都是唯一的,地址值是两个不同的值,所以为false。equals()方法调用父类,也就是Object的
方法
public boolean equals(Object o) {
        return this == o;
    }
通过代码看出,比较的也是地址值,所以返回false。
那么问题来了,怎么实现上述需求?此时,就要重写此方法了。重写的方法中按照自己的逻辑判断就可以了。只需要年龄和名字一致就行。
@Override
    public boolean equals(Object o) {
        if(this == o){
            return true;
        }
        if(o instanceof Student){
            Student s = (Student) o;
            return age == s.age && name.equals(s.name);
        }
        
        return false;
    }
此时,再执行上述两个方法,返回值为 false ,true。
如果说学生信息又加了一个地址address,要这三项都一致才行,那么equals方法稍加变动
 @Override
    public boolean equals(Object o) {
        if(this == o){
            return true;
        }
        if(o instanceof Student){
            Student s = (Student) o;
            return age == s.age && name.equals(s.name) && address.equals(s.address);
        }

        return false;
    }
如果说需求变了,只要名字一致就是一个人,不需要考虑年龄,那么equals方法可以写成下面这样
 @Override
    public boolean equals(Object o) {
        if(this == o){
            return true;
        }
        if(o instanceof Student){
            Student s = (Student) o;
            return  name.equals(s.name);
        }

        return false;
    }
说道这,大家应该明白,可以控制equals方法返回一些信息,明明是不同的地址值,但只要信息内容一致或部分一致,我们都可以当成是一个人来处理。
以上是最基本的用法,下面要重点说数对称性和传递性。
所谓对称,就是s1.equals(s2) == s2.equals(s1),上述代码中肯定可以,但一些特殊的对象写法,一不留神,就会出问题。
比如effective中举的例子,用复合的方式写了一个不区分大小写的String类,
public class CaseString {
    private final String s;

    public CaseString(String s) {
        if(s == null){
            throw new NullPointerException();
        }
        this.s = s;
    }

    @Override
    public boolean equals(Object o) {
        if(o instanceof CaseString){
            return s.equalsIgnoreCase(((CaseString) o).s);
        }
        if(o instanceof String){
            return s.equalsIgnoreCase((String) o);
        }
        return false;
    }
    
}
这个意图很好,想把CaseString 类和 String 类也可以比较,如果
CaseString s1 = new CaseString("abc");
String s2 = "Abc";
s1.equals(s2)返回true,但s2.equals(s1)确实false,因为String类中的equals方法却不知道区分大小写的,此类违反了对称性,因此,既然String类的equals不支持大小写相同,那么对
CaseString类而言,只要不是同类型的都是false就可以了,同类型的话在调用equalsIgnoreCase方法,
@Override
    public boolean equals(Object o) {
        
        return o instanceof CaseString && s.equalsIgnoreCase(((CaseString) o).s);
       
    }

另外一个容易出错的地方是传递性,相同类型的传递性出错基本不可能,出错的一般都是写子类扩展属性的情况。下一章分析。



0 0
原创粉丝点击