如何在Java中避免equals方法的隐藏陷阱

译者注 :你可能会觉得Java很简单,Object的equals实现也会非常简单,但是事实并不是你想象的这样,耐心的读完本文,你会发现你对Java了解的是如此的少。如果这篇文章是一份Java程序员的入职笔试,那么不知道有多少人会掉落到这样的陷阱中。

摘要

本文描述重载equals方法的技术,这种技术即使是具现类的子类增加了字段也能保证equal语义的正确性。

在《Effective Java》的第8项中,Josh Bloch描述了当继承类作为面向对象语言中的等价关系的基础问题,要保证派生类的equal正确性语义所会面对的困难。Bloch这样写到:

除非你忘记了面向对象抽象的好处,否则在当你继承一个新类或在类中增加了一个值组件时你无法同时保证equal的语义依然正确

在《Programming in Scala》中的第28章演示了一种方法,这种方法允许即使继承了新类,增加了新的值组件,equal的语义仍然能得到保证。虽然在这本书中这项技术是在使用Scala类环境中,但是这项技术同样可以应用于Java定义的类中。在本文中的描述来自于Programming in Scala中的文字描述,但是代码被我从scala翻译成了Java

常见的等价方法陷阱

java.lang.Object 类定义了equals这个方法,它的子类可以通过重载来覆盖它。不幸的是,在面向对象中写出正确的equals方法是非常困难的。事实上,在研究了大量的Java代码后,2007 paper的作者得出了如下的一个结论:

几乎所有的equals方法的实现都是错误的!

这个问题是因为等价是和很多其他的事物相关联。例如其中之一,一个的类型C的错误等价方法可能意味着你无法将这个类型C的对象可信赖的放入到容器中。比如说,你有两个元素elem1和elem2他们都是类型C的对象,并且他们是相等,即elem1.equals(elm2)返回ture。但是,只要这个equals方法是错误的实现,那么你就有可能会看见如下的一些行为:

Set hashSet<C> = new java.util.HashSet<C>();
hashSet.add(elem1);
hashSet.contains(elem2);    // returns false!

当equals重载时,这里有4个会引发equals行为不一致的常见陷阱:

定义了错误的equals方法签名(signature) Defining equals with the wrong signature.

重载了equals的但没有同时重载hashCode的方法。 Changing equals without also changing hashCode.

建立在会变化字域上的equals定义。 Defining equals in terms of mutable fields.

不满足等价关系的equals错误定义 Failing to define equals as an equivalence relation.

在剩下的章节中我们将依次讨论这4中陷阱。

陷阱1:定义错误equals方法签名(signature)

考虑为下面这个简单类Point增加一个等价性方法:

public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
// ...
}

时间: 2024-12-31 23:59:04

如何在Java中避免equals方法的隐藏陷阱的相关文章

对象-java中重写equals方法为什么不直接在里面比较hashcode()?

问题描述 java中重写equals方法为什么不直接在里面比较hashcode()? 看书上说只要重写在一个类中重写equals方法,那就一定要重写hashcode方法,因为两个对象只要equals返回值为true,那么他俩的hashcode就一定相同. 那为什么不可以提前先写好hashcode函数,然后在equals函数里面直接来一行if(this.hashcode() == otherObject.hashcode()) return true;else return false;就行了?

JAVA中重写equals()方法的同时要重写hashcode()方法

object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true:注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下:(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2)当obj1.ha

java集合——Java中的equals和hashCode方法详解_java

Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用. equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复. 这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等.换

java中的equals()和toString()方法实例详解_java

java中的equals()和toString()方法 , 这里写个小例子帮助大家学习理解此部分知识. /* 所有对象的父类Object Object中的方法: equals() 对象是否相同的比较方法 toString()对象的字符串表现形式 */ class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } } class ObjectDemo {

如何在java中实现读取一个txt文档中的随机一行

问题描述 如何在java中实现读取一个txt文档中的随机一行 如题,如何在java中实现读取一个txt文档中的随机一行? 主要就是怎么随机读取 解决方案 根据楼上的说法,来总结一下吧,总体来说,就是将文件全部都读取出来,每一行存储到一个数组或集合中,然后再通过产生随机数,来对这个数组或是 集合进行随机的访问.这样一来就解决了 解决方案二: 文本文件只能顺序读,不能随机读.你的需求只能是读取文本文件每一行到一个arraylist,然后得到下标范围,产生一个随机数,取那一行 解决方案三: http:

如何在java中实现依次弹出窗体?

问题描述 如何在java中实现依次弹出窗体? 5C 具体要求如下,窗体都是jframe,主窗体中有n个复选框,每个复选框对应一个窗体,勾选完以后,点击按钮,根据是否选中复选框,依次按顺序从上到下将复选框的窗体弹出,要求在前一个窗体关闭后,才能弹出下一个窗体.这个机制如何实现? 解决方案 了解下 JDialog并且注意它的 setModal(true)属性 解决方案二: 了解下 JDialog并且注意它的 setModal(true)属性 解决方案三: 那怎么知道前一个窗体关了没有,, 解决方案四

浅谈Java中的hashcode方法

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

浅谈Java中的hashcode方法(推荐)_java

哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java的Object类中有一个方法: public native int hashCode(); 根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现. 为何Object类需要这样一个方法?它有什么作用呢?今天我们就来具体探讨一下hashCode方法. 一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到has

java中关于dismiss方法的使用

问题描述 java中关于dismiss方法的使用 myDialog.dismiss( )比如这条语句中是关闭一个对话框的意思吗dismiss还有哪些方面的应用 解决方案 看下这个函数的源码上面的注释信息,jdk源码上的英文注释就是很好的参考文档的. 解决方案二: 这和java语言没有关系,这只是dialog对象定义的方法罢了.你也可以写一个类,定义一个叫dismiss的方法. 在英文字面看来,dismiss就是消失的意思. 解决方案三: java中waitnotifynotifyAll的使用方法