3

Joshua Blotch 的Effective Java指出:

equals除非您愿意放弃面向对象抽象的好处,否则无法在保留契约的同时扩展可实例化类并添加值组件。

Effective Java给出了一些破坏对称性传递性Liskov 替换原则的示例。我想你已经阅读了该项目。我认为我不应该在这里发布整个项目。但是我找到了一个遵守合同的解决方案:

Point:_

public class Point{
    private final int x;
    private final int y;
    public Point(int x, int y){
        this.x = x;
        this.y = y;
    }
    protected boolean equalsImpl(Point p){
        return p.x == x && p.y == y;
    }
    @Override
    public final boolean equals(Object o) {
        if(!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return equalsImpl(p) && p.equalsImpl(this);
    }
    // Remainder omitted ...
}

ColorPoint:_

public class ColorPoint extends Point{
    private java.awt.Color color;
    public ColorPoint(int x, int y, java.awt.Color color){
        super(x, y);
        this.color = color;
    }
    @Override
    protected boolean equalsImpl(Point p){
        return (p instanceof ColorPoint) &&
                super.equalsImpl(p) &&
                ((ColorPoint)p).color == color;
    }
    // Remainder omitted ...
}

我知道在这种情况下应该使用组合,但我想知道我的解决方案是否真的遵守equals合同。如果是的话,Effective Java声明的错误是什么

4

4 回答 4

3

有了这个实现 noColorPoint等于 a Point,所以它打破了 Liskov 替换原则。但它确实履行了equals合同。

更新: LSP 是否损坏取决于Point该类所拥有的合同。如果您有一个合同,上面写着“具有相同 X 和 Y 坐标的两个点相等”或“您可以使用 aHashSet来消除具有重复坐标的点”,它就被破坏了。如果不存在这样的合同,LSP 不会被破坏。

另一方面,如果 noColorPoint等于 a ,那么 a is-a是否Point有意义?ColorPointPoint

于 2013-06-22T07:40:48.543 回答
0

equals合同不要求 LSP 。

也许这个问题是在equals实现主题上不必要地包含 LSP 的结果。标准 eclipse 生成的equals实现已经使用super.equals(利用继承)并且比建议的解决方案更正确且更简单。但是,它不符合 LSP 的标准,根据文档是可以的。

于 2013-06-22T08:35:25.040 回答
0

我想知道什么是 LSP,所以我遇到了一篇好文章。正如文章所说 ,里氏替换原则在 Java 中不成立

LSP 对属性集定义了一个弱约束,但随后声称对于任何属性都可以证明程序行为是相同的。唯一可以验证的方法是,系统中的原始对象是否由其规范定义得如此严格,以至于它们不允许其他实现。例如,上述论文的作者声称,给定一个方法的一组属性,他们可以声称与另一个具有相同合约的方法 100% 的行为兼容性——这不是真的。本质上,我可以声称 Java 中 toString() 方法的约定非常弱——“返回对象的字符串表示形式”。

为什么里氏替换原则很重要?

  1. 因为如果不是这样,那么类层次结构就会一团糟。麻烦的是,每当子类实例作为参数传递给任何方法时,都会发生奇怪的行为。
  2. 因为如果不是这样,超类的单元测试将永远不会为子类成功。

参考链接,可以消除你的疑惑。

于 2013-06-22T08:37:18.747 回答
0

equals如果基类的约定允许派生类这样做,并且equals基类的方法是这样编码的,那么在遵守类和 LSP 的同时让可实例化类添加值组件是没有困难的一种方法

  1. 检查与之比较的任何事物的确切类型,并报告false确切类型是否不匹配。

  2. 定义一个方法,将其用作相等性检查过程的一部分,派生类可以根据需要覆盖该方法。

使用方法#1,任何派生类都可以定义equals考虑新字段而不会产生任何困难,因为唯一可能认为派生类实例相等的事物是同一派生类的其他实例。

使用方法 #2 有点棘手,但一个简单的方法可能是重写equals一些东西来调用protectedequals2 方法,如下所示:

@override boolean equals(Object other)
{
  if (!(other instanceof MyType)) return false;
  MyType otherAsMyType = (MyType)other;
  return this.equals1(OtherAsMyType) && OtherAsMyType.equals1(this) &&
         this.equals2(OtherAsMyType) && OtherAsMyType.equals2(this);   
}

派生类型的equals1方法在传递不是从它派生的东西时会返回false,即使其他对象对派生部分一无所知;将比较分成不同的部分将确保如果一个类型可以快速确定另一个类型的对象不可能与它相等,则第一个类中的代码不会浪费时间检查看似相等的部分。

于 2014-06-24T19:24:55.707 回答