`
yangdong
  • 浏览: 66624 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

多层类结构的对象相等性

    博客分类:
  • Java
阅读更多
Effective Java 中,Joshua Bloch 提到,如果一个可实例化的类定义了 equals 方法。另有一个子类继承它,也定义了额外一些属性,并且 equals 方法中需要使用这些新定义的属性进行相等性判断。那么就不可能保证 equals 语义的正确。

相信看过 Effective Java 的人当年读到这里时都会觉得丧气。就好像完美的世界突然有了一个无法缝合的裂口。先不要完全丧失兴趣,看看下面的文章:
How to Write an Equality Method in Java

这篇主要由 Scala 的作者 Martin Odersky 执笔的文章中提到了一个有意思的方法。每个类在定义 equals 时,首先先判断 canEqual 能不能校验通过。canEqual 的作用就是限定:只有当被比较的对象是当前对象的子类或同类时才能通过。
class Point {

  // 属性定义
  ...

  boolean canEqual(Object other) {
    return (other instanceof Point);
  }
  
  @Override boolean equals(Object other) {
    if (other instanceof Point) {
      Point that = (Point) other;
      if (that.canEqual(this) && getX() == that.getX() && getY() == that.getY()) return true;
    }
    return false; 
  }
  
}

子类的定义与父类相似。
class ColoredPoint extends Point {

  // 额外的属性定义
  ...

  boolean canEqual(Object other) {
    return (other instanceof ColoredPoint);
  }
  
  @Override boolean equals(Object other) {
    if (other instanceof ColoredPoint) {
      ColoredPoint that = (Point) other;
      if (that.canEqual(this) && color.equals(that.color) && super.equals(that)) return true;
    }
    return false; 
  }
  
}


也就是说,在这样的约定下,如果拿一个重载了 canEqual 的子类的实例和一个父类实例比较肯定会返回 false。关于这篇文章,有兴趣的话可以看看相应的讨论

讨论主要集中在文章里的方法是否违背了 Liskov Substitution Principle (LSP),以及如果违背了那么这个问题有多严重上。看过下面的分析后大家也许会觉得这种讨论没有太多意义。

我个人推荐这种 canEqual 方法。我说“方法”而不说“解决方案”是因为我觉得 Odersky 所描述的 equals 实现与 Bloch 本来所期望的 equals 逻辑模型并不一致。想像 Odersky 文章中的例子。有一个类 - 点(Point),及其子类 - 有色点(ColoredPoint)。如果一个有色点实例,其坐标与一个普遍点坐标一样,又因为有色点“是”点,所以这两点应该“相等”。大家都期望这样一个结论是成立的,所以当看到 Bloch 的结论时会觉得面向对象有其固有的自相矛盾之处。但是这样一个结论却并不是天然成立的。一个没有颜色的点与一个有颜色的点能相等吗?有人会说,如果 ColoredPoint 里面的 color 属性是一个枚举,而且那个子类被实例化成 Color.UNSPECIFIED(未指定的颜色),那么这两个点逻辑上就应该相等了吧。我认为,如果 ColoredPoint.color 可以有这样一个属性值的话,那么 Point 类就应该被定义为抽象类。Point 类此时实例化没有意义。换句话说,如果 Point 类可以实例化,且其子类 ColoredPoint 也可以有一个“未指定的颜色”,而且两者都定义了 equals,那么出现这种情况我认为是设计失败。

再看看 LSP。LSP 说,任何可以使用父类实例的地方都可以使用子类实例代替。这里并不违反 LSP,因为如果一个地方可以这样调用:
Point p = new Point();
if (p.equals(...)) {
  ...
}

那么使用子类一样可以调用 equals。只不过,equals 在传入相同的参数时返回的结果可能会不一样。但是 LSP 并不约束必须返回一样的结果。而这正是多态的特征。

回到 Bloch 的论点上。现在赞同我的人可能会觉得 Bloch 的论点有问题。其实他说得很严谨,没有一丝问题。他的论点的前提是:可实例化的父类。也就是说无法针对非抽象类写出满足大家传统期望的子类。只不过,另人失望地,他在提出这个结论后没有给出对应的方法。相对来说,Odersky 理清了 Bloch 的逻辑模型。所以,在 Odersky 所发明的 Scala 中,canEqual 这个方法也被作为官方推荐的 equals 实现方法。
1
0
分享到:
评论

相关推荐

    java面向对象程序设计习题集.doc

    - **多层嵌套**:如何分析多层嵌套的控制结构,如多个循环嵌套、循环与条件语句的组合等。 ##### 五、编程题 这部分题目鼓励学生动手实践,运用所学知识解决实际问题。 **知识点**: - **算法实现**:如何利用...

    ----------群硕面试题------

    - 对象一致性和对象相等通常指引用是否相同(一致)和内容是否相同(相等)。 - .NET中的深拷贝是创建一个新对象,复制所有字段,包括嵌套对象的深度复制。 - ICloneable接口提供了自定义复制行为的机会。 9. ...

    examen practiko

    7. **多层继承和层次结构**:在给定的类图中,Manager类是从Employee接口派生的,这涉及到继承的概念。同时,Manager类可能需要管理一个Employee类型的集合,这反映了类之间的关联关系和层次结构。 具体任务是设计...

    超级有影响力霸气的Java面试题大全文档

     多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。 5、String是最基本的数据类型...

    DotNet面试题汇总.pdf

    36. **.NET多层结构**:常见的架构有三层架构(表示层、业务逻辑层、数据访问层)或多层架构,每层之间通过接口通信,目的是为了提高系统的灵活性和可维护性。 37. **应用程序域**:应用程序域是.NET框架中的一个轻...

    2019年最新版修订版Java程序员面试宝典.pdf

    12. ==与equals的区别:==操作符比较的是两个变量的值是否相等,而equals方法比较的是两个对象的内容是否相等。 13. break和continue的区别:break用于完全结束循环,而continue用于结束当前循环,并开始下一次循环...

    asp.net面试试题

    4. 多层架构:了解如何设计和实现多层结构,如表现层、业务逻辑层和数据访问层,有助于保证代码的可维护性和可扩展性。 5. 网站安全与性能:开发者需要知道如何识别和防止常见的安全漏洞,如 SQL 注入、跨站脚本...

    2021-2022计算机二级等级考试试题及答案No.15656.docx

    11. 对象相等性和哈希码:在Java等语言中,如果两个对象通过`equals()`方法比较相等,那么它们的哈希码应该也相等,以便在哈希表(如HashSet或HashMap)中正确地处理对象。如果不考虑存储在哈希结构中,对象的哈希码...

    java常见面试题

    `==`用于比较两个变量是否指向同一个对象,而`equals`方法用于比较两个对象的内容是否相等。 #### 12\. 静态变量和实例变量的区别? 静态变量属于类级别,所有实例共享同一份,实例变量属于对象级别,每个对象有一...

    javascript教程

    关系运算符用于比较两个值之间的关系,而等性运算符用于判断两个变量是否相等。条件运算符(三元运算符)是JavaScript中唯一的三元运算符,它根据条件表达式的结果来决定输出哪个值。 在JavaScript中,语句是构成...

    java就业面试题 java面试 java考试 java学习

    "==" 比较的是两个对象的内存地址,而 equals() 方法(默认行为比较引用是否相等)在自定义类中通常用来比较对象的内容是否相等。对于基本数据类型,"==" 和 equals() 行为一致。 13. **静态变量和实例变量的区别...

    Delphi2010语法手册.pdf

    - **比较表达式**:用于比较两个值的大小、相等性等。 - **逻辑表达式**:包含逻辑运算符的表达式。 **语句** - **简单语句**:单一操作,如赋值语句。 - **复合语句**:由多个简单语句组成的语句块。 **块和域**...

    java面试资料2019

    - `equals`方法用于比较两个对象的内容是否相等,默认情况下,它也是比较引用地址。 #### 12. 静态变量与实例变量 - **静态变量**:属于类的,类的所有对象共享同一份副本;存储在方法区。 - **实例变量**:属于...

    智能控制整理.doc

    传统控制方法,如经典控制和现代控制,主要依赖于被控对象的精确模型,适用于线性、时不变的系统,但在面对高度非线性、不确定性模型的控制任务时显得力不从心。 智能控制引入了学习、适应、抽象、推理和决策等能力...

    java面试面霸

    11. **"=="与equals的区别**:"=="是比较操作符,用于比较两个变量或对象的引用是否相等,而equals方法是Object类中的方法,用于比较对象的内容是否相等。默认情况下,equals与"=="行为一致,但在自定义类中,需要...

    Java面试宝典2014版

    内部类可以直接访问外部类的所有成员,包括私有成员,但外部类不能直接访问内部类的成员,除非使用内部类对象。 #### 28. 匿名内部类的特点 - **是否可以继承其他类**:不可以。 - **是否可以实现接口**:可以。 #...

    spatial_pyramid_backup.rar_Piotr Dollar_pyramid_spatial pyramid_

    空间金字塔的核心思想是将原始图像分成多个不同尺度的子区域,形成一个多层的金字塔结构。每一层金字塔代表了不同分辨率下的图像特征,底层包含较小的子区域,具有较高的细节信息,而高层则包含较大的子区域,提供更...

    《Java虚拟机精讲》PDF版本下载.txt

    类加载器体系结构由多个类加载器组成,包括启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用类加载器(Application ClassLoader)。每个类加载器负责加载特定目录下的类库。 #...

    java面试宝典 吐血推荐,很全面

    `equals`方法比较的是两个对象的内容是否相等。 - **注意事项**: 在比较对象时,如果对象重写了`equals`方法,则`equals`将根据重写的逻辑来比较对象。 **12. 静态变量和实例变量的区别?** - **知识点**: 静态...

Global site tag (gtag.js) - Google Analytics