Effective Java 第三版读书笔记(对于所有对象都通用的方法1)

第10条.覆盖equals时请遵守通用约定

Object默认的equals方法会比较对象等同,如果类具有自己特有的“逻辑相等”的概念,我们可以覆盖equals方法。在覆盖equals方法的时候,必须遵守以下通用约定:

1.自反性:对于任何非null的引用值x,x.equals(x)必须返回true。

2.对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

package com.example.ownlearn;

import java.util.Objects;

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s){
        this.s = Objects.requireNonNull(s);
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CaseInsensitiveString){
            return s.equalsIgnoreCase(((CaseInsensitiveString)obj).s);
        }
        //重构需要去掉
        if(obj instanceof  String)
        return s.equalsIgnoreCase((String)obj);
        return false;
        //重构需要去掉
    }
}

在这个类中,equals方法的意图非常好,他企图与普通的字符串对象进行互操作。假设我们有一个不区分大小写的字符串和一个普通的字符串:

    CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
    String s = "polish";
    Boolean a = cis.equals(s);
    Boolean b = s.equals(cis);
    List<CaseInsensitiveString> list = new ArrayList<>();
    list.add(cis);
    boolean c = list.contains(s);

a的值为true,b的值为false,因为在Stirng的equals方法中比较的时候并没有区分大小写,这显然是违反对称性的,c在这里碰巧返回了一个false,在其他实现中,有可能为true,甚至抛出异常。为了解决这个问题,我们可以将与String互操作的这段代码从equals方法中去掉就可以了。

3.传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。

子类增加的信息会影响equals的比较结果。

package com.example.ownlearn;

public class Point {
    private final int x;
    private final int y;

    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }

    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Point))
            return false;
        Point p = (Point)obj;
        return p.x == x && p.y == y;
    }
}

我们现在有一个描述点的类,重写他的equals方法分别对横坐标、纵坐标进行比较来判断是否相等。

现在由于业务扩展我们有了Point的一个自类,我们对原来的字段进行拓展。

package com.example.ownlearn;

import java.awt.*;

public class ColorPoint extends Point {
    private final Color color;
    public ColorPoint(int x, int y,Color color) {
        super(x, y);
        this.color = color;
    }
}

那么对于子类的equals方法的实现我们有以下几种做法:

1).直接继承父类的equals方法,这样做是没有问题的,但是并没有对Color进行比较,这样做是有问题的。

2).只有当他的参数是另一个有色点,并且具有相同的位置和颜色时,它才会返回true。

 @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof ColorPoint))
            return false;
        return super.equals (obj) && ((ColorPoint) obj).color == color;
    }

测试:

  Point point = new Point(1,2);
        ColorPoint colorPoint = new ColorPoint(1,2,Color.BLACK);
        boolean a = point.equals(colorPoint);
        boolean b = colorPoint.equals(point);

返回的结果是:a 的值为true,b的值为false,很明显这是不符合对称性的。

我们通过修改代码来修正这个问题,让ColorPoint.equals在进行混合比较时忽略颜色信息。

    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Point))
            return false;
        if(!(obj instanceof ColorPoint))
            return  super.equals (obj);
        return super.equals (obj) && ((ColorPoint) obj).color == color;
    }

测试:

 Point point = new Point(1,2);
        ColorPoint colorPoint = new ColorPoint(1,2,Color.BLACK);
        ColorPoint colorPoint1 = new ColorPoint(1,2,Color.BLUE);
        boolean a = point.equals(colorPoint);
        boolean b = point.equals(colorPoint1);
        boolean c = colorPoint.equals(colorPoint1);

结果a=true,b=true,c=false。违反了传递性。

我们在无法在扩展实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面对对象的抽象所带来的优势。

一种不错的权宜之计是,我们不再让ColorPonit扩展Point,而是在ColorPoint加入一个私有的Point域,以及一个公有的视图方法。

 @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof  ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint)obj;
        return cp.point.equals(point) && cp.color.equals(color);
    }

Java平台类库中,有一些类扩展了可实例化的类,并添加了新的值组件。例如,java.sql.Timestamp对java.util.Data进行了扩展,并增加了nanoseconds域。Timestamp的equals确实违反了对称性,如果Timestamp和Date对象用于同一个集合中,或者以其他方式被混合在一起,则会引起不正确的行为。

注意:可以在一个抽象类的自类中增加新的值组价且不违反equals约定。因为抽象类的实例无法被直接创建。

4.一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致的返回false。

5.对于任何非null的引用值x,x.equals(null)必须返回false。

为了进行比较,我们需要把参数转换成适当的类型,在进行转换前,必须使用instance操作符,检查其参数的类型是否正确。

 if(!(obj instanceof  ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint)obj;

结合以上要求,得出以下实现高质量equals方法的诀窍:

1.使用==操作符检查“参数是否为这个对象的引用”,如果是,返回true。

2.使用instance操作符号检查“参数是否为正确的类型”,如果不是返回false。

3.把参数转换成正确的类型。

4.对于该类中每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配,如果这些测试全部成功,则返回true。

需要注意的是:

1)覆盖equals时总要覆盖hashCode

2)不要企图让equals方法过于智能。

3)不要将equals声明中的Onbject对象替换为其他类型

可以使用Google的开源AutoValue框架,他会自动生成这些方法,不要轻易覆盖equals方法,除非迫不得已。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值