.Contains()方法未调用重写的equals方法

35
我有一个问题,我创建了一个Foo对象的ArrayList,我覆盖了equals方法,但是无法使contains方法调用equals方法。我尝试一起覆盖equals和hashCode,但仍然不起作用。我相信这其中有一个逻辑解释,但是目前自己无法找到。 我只是想找到一种方法来检查列表中是否包含指定的id。
以下是一些代码:
import java.util.ArrayList;
import java.util.List;

public class Foo {

    private String id;


    public static void main(String... args){
        Foo a = new Foo("ID1");
        Foo b = new Foo("ID2");
        Foo c = new Foo("ID3");
        List<Foo> fooList = new ArrayList<Foo>();
        fooList.add(a);
        fooList.add(b);
        fooList.add(c);
        System.out.println(fooList.contains("ID1"));
        System.out.println(fooList.contains("ID2"));
        System.out.println(fooList.contains("ID5"));
    }   

    public Foo(String id){
        this.id = id;
    }

    @Override
    public boolean equals(Object o){
        if(o instanceof String){
            String toCompare = (String) o;
            return id.equals(toCompare);
        }
        return false;
    }



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

输出: false false false


为什么检查一个字符串是否包含愚蠢的内容? - Prabhat Gaur
1
我大约9年前问过这个问题。现在我知道这没有意义,但当时我不理解。由于这个问题的答案,我现在明白了.equals()如何工作。 - Reid Mac
3个回答

48

这是因为你的equals()方法不具有对称性

new Foo("ID1").equals("ID1");

但是

"ID1".equals(new Foo("ID1"));

这是不正确的。这违反了 equals() 契约:

equals 方法在非空对象引用上实现了等价关系:

  • [...]

  • 它是对称的:对于任何非空引用值 xy,如果 x.equals(y) 返回 true,则 y.equals(x) 应该返回 true。

但它也不是自反的

  • 它是自反的:对于任何非空引用值 xx.equals(x) 应该返回 true。
Foo foo = new Foo("ID1");
foo.equals(foo)  //false!

@mbockus 提供了正确实现 equals() 的方法:

public boolean equals(Object o){
  if(o instanceof Foo){
    Foo toCompare = (Foo) o;
    return this.id.equals(toCompare.id);
  }
  return false;
}

但现在你必须将 Foo 的实例传递给 contains()

System.out.println(fooList.contains(new Foo("ID1")));
System.out.println(fooList.contains(new Foo("ID2")));
System.out.println(fooList.contains(new Foo("ID5")));

最后,你应该实现hashCode()以提供一致的结果(如果两个对象是相等的,则它们必须具有相等的hashCode()):

@Override
public int hashCode() {
    return id.hashCode();
}

1
@ReidMac:我错了,这与equals()不对称有关,请看看我的编辑。在这种情况下,hashCode()与此无关,但仍应遵循这个原则。 - Tomasz Nurkiewicz
奇怪的是我们需要使用new Foo("ID1");这种方式来使用自定义equals方法。这背后有什么原因吗? - coretechie

11

你的equals方法需要进行修改,并且还要重写hashCode()函数。目前,你正在检查要比较的对象是否为String的一个实例,而你需要检查的是Foo对象。

public boolean equals(Object o){
    if(o instanceof Foo){
        Foo toCompare = (Foo) o;
        return this.id.equals(toCompare.id);
    }
    return false;
}

如果您使用的是 Eclipse,我建议通过转到 Source -> Generate hashcode() and equals() 让 Eclipse 为您生成 hashCode 和 equals 方法...


1
+1,我把你的代码片段复制到了我的答案中,希望你不介意。 - Tomasz Nurkiewicz

6

你应该实现hashCode方法

@Override
public int hashCode() {
    return id.hashCode();
}

即使不使用它,contains方法也可以用于ArrayList。但是,您面临的主要问题是您的equals方法只接受String类型,而不是Foo对象,并且您使用字符串调用了contains方法。如果实现让列表中的每个元素都判断是否等于您发送的字符串,则代码可以正常运行,但是实现会询问字符串是否等于您的Foo对象,这显然是不可能的。

请使用equals方法

@Override
public boolean equals(Object o){
    if(o instanceof Foo){
        String toCompare = ((Foo) o).id;
        return id.equals(toCompare);
    }
    return false;
}

然后检查包含内容

System.out.println(fooList.contains(new Foo("ID1")));

2
使用HashSet时,在contains()检查中我的equals()方法根本没有被调用,直到我也添加了hashCode()方法。 - frances

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接