8、覆盖equas时请遵守约定

覆盖equas时请遵守约定

不覆盖的情况

类的每个实例本质上是唯一的(Object本身的equals就能产生正确的行为)
不关心逻辑是否相等
基类已经覆盖了equals,并且从基类继承下来是合适的
类是私有的或是包级私有的,可以确定它的equals方法永远不会调用(这是用可以在它的equals方法中抛出异常,防止调用)

什么时候覆盖

如果类具有自己的逻辑相等的概念的时候,并且基类还没有实现。这也叫“值类”
有一种值类不需要覆盖equals方法,即单例模式的情况下,逻辑相同和对象等同是同一回事,所以不需要覆盖equals方法

equals等价关系的性质

自反性
对称性

比如下面一个不区分大小写的类,这个类的对象调用equals,并传入普通的String类,和普通的String类调用equals并传入这个类的结果是不同的

public final class CaseInsensitiveString {
    private final String s;

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

    // Broken - violates symmetry!
    @Override
    public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
        //这句要删掉,不然会出现自反性
        if (o instanceof String) // One-way interoperability!
            return s.equalsIgnoreCase((String) o);
        return false;
    }

    // This version is correct.
    // @Override public boolean equals(Object o) {
    // return o instanceof CaseInsensitiveString &&
    // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
    // }

    public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
        String s = "polish";
        System.out.println(cis.equals(s) + "  " + s.equals(cis));
    }
}
传递性

考虑一个Point基类:

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 o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point) o;
        return p.x == x && p.y == y;
    }

    // See Item 9
    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}

它的子类ColorPoint:

public class ColorPoint extends Point {
    //多加了一个颜色的属性
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }

    // Broken - violates symmetry!
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        //增加了判断颜色,如果不是该类,就返回false,如果是,强转后判断
        return super.equals(o) && ((ColorPoint) o).color == color;
    }
    public static void main(String[] args) {
        // First equals function violates symmetry
        Point p = new Point(1, 2);
        ColorPoint cp = new ColorPoint(1, 2, Color.RED);
        //结果是true、false, 违反了对称性
        System.out.println(p.equals(cp) + " " + cp.equals(p));
    }
}

可以改成:

     Broken - violates transitivity!
     @Override public boolean equals(Object o) {
     //过滤掉不是Point或者其子类的类
     if (!(o instanceof Point))
     return false;

     // If o is a normal Point, do a color-blind comparison
     //用来解决对称性,调用普通类的比较方法,忽略颜色信息
     if (!(o instanceof ColorPoint))
     return o.equals(this);

     // o is a ColorPoint; do a full comparison
     return super.equals(o) && ((ColorPoint)o).color == color;
     }

但是这样会出现传递性的问题

// Second equals function violates transitivity
    ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
    Point p2 = new Point(1, 2);
    ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
    //true, true, false
    //违反了传递性
    System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3),
            p1.equals(p3));

所以问题怎么解决:

实际上,我们无法扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定

当然你也可以这样做:

     Broken - violates Liskov substitution principle - Pages 39-40
     @Override public boolean equals(Object o) {
     if (o == null || o.getClass() != getClass())
     return false;
     Point p = (Point) o;
     return p.x == x && p.y == y;
     }

在基类中用getclass代替instanceof。这样必须是相同的实现类才可以判断。但是继承性被忽略了(因为你继承的时候不一定是添加了新的值组件)

一个权宜之计: 采用组合的形式

//color的枚举
public enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}

point类和之前的一样

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 o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point) o;
        return p.x == x && p.y == y;
    }

    // See Item 9
    @Override
    public int hashCode() {
        return 31 * x + y;
    }
}
//采用组合形式的colorpoint类
public class ColorPoint {

    private final Point point;
    private final Color color;

    public ColorPoint(int x, int y, Color color) {
        if (color == null)
            throw new NullPointerException();
        point = new Point(x, y);
        this.color = color;
    }

    /**
     * Returns the point-view of this color point.
     */
    public Point asPoint() {
        return point;
    }

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

    @Override
    public int hashCode() {
        return point.hashCode() * 33 + color.hashCode();
    }
}

为什么采用组合的形式可以:

因为没有了继承,instanceof 的判断就很精确

注意:

抽象类的子类中增加新的值组件,而不会违反equals的约定
一致性

无论类是否不可变的,都不要依赖于不可靠的资源

非空性

显然对象是空的,equals都调用不了

通常我们在写equals方法的时候,也会加入是否为空引用的判断,但是这样写是不必要的,因为instanceof本身就会对空引用的判断返回false

写equals的实际步骤

判断是否为同一个引用(使用“==”判断)
使用instanceof判断类型
把参数转换成正确的类型
执行判断逻辑

注意事项:

覆盖equals的时候要覆盖hashcode
不要让equals过于只能
不要将equals的参数Object换成其他的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值