重写Java equals()方法-不起作用?


150

equals()今天,我遇到了一个有趣(非常令人沮丧)的方法问题,该问题导致我认为是经过良好测试的类崩溃了,并导致了一个错误,使我花了很长时间来追踪。

为了完整起见,我没有使用IDE或调试器-只是老式的文本编辑器和System.out。时间非常有限,这是一个学校项目。

无论如何-

我开发一个基本的购物车可能包含ArrayListBook对象。为了贯彻落实addBook()removeBook()以及hasBook()对车的方法,我想检查,如果Book在已经存在Cart。所以我走了-

public boolean equals(Book b) {
    ... // More code here - null checks
    if (b.getID() == this.getID()) return true;
    else return false;
}

在测试中一切正常。我创建6个对象,并用数据填充它们。在上执行许多添加,删除,has()操作Cart,一切正常。我读到您可以拥有equals(TYPE var)equals(Object o) { (CAST) var }可以假设自从它开始工作以来,并没有太大关系。

然后,我遇到了一个问题-我需要创建一个Book与对象ID从Book类内它。没有其他数据可以输入。基本上如下:

public boolean hasBook(int i) {
    Book b = new Book(i);
    return hasBook(b);
}

public boolean hasBook(Book b) {
    // .. more code here
    return this.books.contains(b);
}

突然,该equals(Book b)方法不再起作用。如果没有良好的调试器,并且假设Cart该类经过了正确的测试和纠正,这将花费很长时间。将equals()方法交换为以下内容之后:

public boolean equals(Object o) {
    Book b = (Book) o;
    ... // The rest goes here   
}

一切又开始工作了。该方法是否有理由决定不采用Book参数,即使该参数很明显一个Book对象呢?唯一的区别似乎是它是在同一类中实例化的,并且仅填充了一个数据成员。我很困惑。拜托,让我们亮一下吗?


1
我知道我违反了通过反射来覆盖equals方法的“合同”,但是我需要一种快速的方法来检查对象是否存在于ArrayList中而不使用泛型。
乔什·史密顿

1
这是一个很好的教训,了解Java和等于
jjnguy

Answers:


329

在Java中,equals()从其继承的方法Object是:

public boolean equals(Object other);

换句话说,参数必须是类型Object。这称为覆盖 ; 你的方法public boolean equals(Book other)做什么叫做超载equals()方法。

ArrayList重写应用equals()方法来比较的内容(例如,用于其contains()equals()方法),超载的。在您的大多数代码中,调用未正确覆盖Objectequals的代码是可以的,但与并不兼容ArrayList

因此,不正确地重写方法会导致问题。

我每次都覆盖等于:

@Override
public boolean equals(Object other){
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MyClass)) return false;
    MyClass otherMyClass = (MyClass)other;
    ...test other properties here...
}

使用@Override注释可以帮助解决许多愚蠢的错误。

只要您认为自己要重写超类或接口的方法,就可以使用它。这样,如果以错误的方式进行操作,则会出现编译错误。


31
这是支持@Override批注的一个很好的论据...如果OP使用@Override,则他的编译器会告诉他他实际上并没有覆盖父类方法...
Cowan

1
从来没有意识到@Override,谢谢!我还想补充一点,实际上应该已经完成​​了覆盖hashCode()的工作,并且可能会更快地发现该错误。
乔什·史密顿

5
某些IDE(例如Eclipse)甚至可以根据类成员变量为您自动生成equals()和hashcode()方法。
sk。

1
if (!(other instanceof MyClass))return false;返回false是否MyClass扩展另一个类。但是false如果另一个类扩展了,它不会返回MyClass。应该不equal那么矛盾吗?
罗伯特

19
当使用instanceof时,以前的nullcheck是多余的。
Mateusz Dymczyk

108

如果您使用eclipse,请转到顶部菜单

源->生成equals()和hashCode()


我同意!我以前从未知道过并生成该代码,它减少了出错的可能性
2014年

同样在这里。谢谢弗雷德!
阿妮拉

16
在IntelliJ中,您可以在“代码”→“生成...”或“ control + N”下找到它。:)
2014年

在Netbeans中,转到菜单栏>源(或右键单击)>插入代码(或Ctrl-I),然后单击生成equals()...
Solomon

11

您的问题有点偏离主题,但无论如何值得一提:

Commons Lang有一些出色的方法可用于覆盖equals和hashcode。看看EqualsBuilder.reflectionEquals(...)HashCodeBuilder.reflectionHashCode(...)。过去为我省去了很多麻烦-尽管当然,如果您只想对ID进行“等于”操作,则可能不适合您的情况。

我也同意,@Override只要您覆盖等号(或任何其他方法),就应该使用注释。


4
如果你是一个Eclipse用户,你也可以去right click -> source -> generate hashCode() and equals()
tunaranch

1
这些方法是在运行时执行的,对吗?如果我们遍历一个大集合并且由于反射而检查它们是否与其他项目相等,我们是否会遇到性能问题?
加盖

4

另一种节省样板代码的快速解决方案是Lombok EqualsAndHashCode批注。简单,优雅且可自定义。并且不依赖于IDE。例如;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{

    private long        errorNumber;
    private int         numberOfParameters;
    private Level       loggingLevel;
    private String      messageCode;

请参阅可用于自定义在等号中使用哪些字段的选项。龙目岛(Lombok)在专家中很受欢迎。只需添加提供的作用域即可:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
    <scope>provided</scope>
</dependency>

1

在Android Studio中为alt + insert ---> equals和hashCode

例:

    @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Proveedor proveedor = (Proveedor) o;

    return getId() == proveedor.getId();

}

@Override
public int hashCode() {
    return getId();
}

1

考虑:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...

1
@Elazar怎么回事?obj被声明为Object。继承的重点是您可以将分配Bookobj。在那之后,除非您建议an Object不应该与Stringvia 相提并论,否则equals()此代码应完全合法并返回false
bcsb1001 2016年

我完全建议。我相信它已经被广泛接受。
Elazar

0

instanceOf语句通常用于实现equals。

这是一个普遍的陷阱!

问题是使用instanceOf违反了对称规则:

(object1.equals(object2) == true) 当且仅当 (object2.equals(object1))

如果第一个equals为true,并且object2是obj1所属的类的子类的实例,则第二个equals将返回false!

如果ob1所属的被视为类声明为final,则不会出现此问题,但通常应测试如下:

this.getClass() != otherObject.getClass(); 如果不是,则返回false,否则请测试字段以进行相等性比较!


3
请参阅Bloch,《有效Java》,第8项,其中一大部分讨论了覆盖该equals()方法的问题。他建议不要使用getClass()。主要原因是这样做违反了不影响相等性的子类的Liskov替代原理。
Stuart Marks 2015年

-1

recordId是对象的属性

@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Nai_record other = (Nai_record) obj;
        if (recordId == null) {
            if (other.recordId != null)
                return false;
        } else if (!recordId.equals(other.recordId))
            return false;
        return true;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.