List.contains() 失败,而 .equals() 工作

Posted

技术标签:

【中文标题】List.contains() 失败,而 .equals() 工作【英文标题】:List.contains() fails while .equals() works 【发布时间】:2016-05-27 07:43:34 【问题描述】:

我有一个 ArrayListTest 对象,它们使用字符串作为等效检查。我希望能够使用List.contains() 来检查列表是否包含使用某个字符串的对象。

简单地说:

Test a = new Test("a");
a.equals("a"); // True

List<Test> test = new ArrayList<Test>();
test.add(a);
test.contains("a"); // False!

等于和哈希函数:

@Override
public boolean equals(Object o) 
    if (o == null) return false;
    if (o == this) return true;
    if (!(o instanceof Test)) 
        return (o instanceof String) && (name.equals(o));
    
    Test t = (Test)o;
    return name.equals(t.GetName());


@Override
public int hashCode() 
    return name.hashCode();

我读到了确保contains 适用于自定义类,它需要覆盖equals。因此,我很奇怪equals 返回 true,而 contains 返回 false。

我怎样才能做到这一点?

Full code

【问题讨论】:

一个类只能用同一个类测试equals 您对equals 的作用的想法是错误的,您应该将TestTest 的另一个实例进行比较 只有反过来才行,因为"a".equals(new Test("a")) == false 另外,"a".equals(new Test("a")) != new Test("a").equals("a") 是个问题。 @bayou.io Object.equals 的合约默认需要它,因为非空equality 的交换属性。 【参考方案1】:

equals() 应始终为 commutative,即 a.equals(b)b.equals(a) 应始终返回相同的值。或者 对称,正如 equals() 的 javadoc 所称:

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

它是自反的:对于任何非空引用值xx.equals(x) 应该返回true它是对称的:对于任何非空引用值xy,当且仅当y.equals(x) 返回@987654336 时x.equals(y) 应该返回true @。 它是可传递的:对于任何非空引用值xyz,如果x.equals(y) 返回truey.equals(z) 返回true ,然后x.equals(z) 应该返回true一致:对于任何非空引用值 xyx.equals(y) 的多次调用始终返回 true 或始终返回 false,未提供任何信息修改了用于equals 对象比较的对象。 对于任何非空引用值xx.equals(null) 应返回false

不幸的是,即使是 Java 运行时库也弄错了这一点。 Date.equals(Timestamp) 将比较毫秒值,忽略 Timestamp 中存在的纳秒,而 Timestamp.equals(Date) 返回 false

【讨论】:

+1 保持非对称 equals 会使代码变得不必要地脆弱且难以维护,因为它依赖于完成循环测试的方式。 JDK 中有更大的不对称性 - Map 接口。如果您想知道为什么 Map 有 get(Object key) 而不是 get(K key) - 正是为了允许传递碰巧破坏了对 K 实例的相等性的随机类。在 Java 的一个版本中,Sun 更改了此顺序(因此将 map 中的值与传递的键进行比较,而不是其他方式),它破坏了很多应用程序,他们不得不依靠它。【参考方案2】:

如果你写

test.contains(new Test("a")); 

那么它肯定会返回true。您正在检查测试列表中的字符串对象。

【讨论】:

所以我们需要使用contains调用创建一个新对象?【参考方案3】:

问题是 List&lt;E&gt;.contains(object o) 被记录为返回 true:

当且仅当此列表包含至少一个元素 e 使得 (o==null ? e==null : o.equals(e))。

(来自https://docs.oracle.com/javase/8/docs/api/java/util/List.html#contains-java.lang.Object-)

请注意,它不会像 e.equals(o) 那样执行测试,这是您的测试工作所必需的。您的 equals 方法无法以交换方式工作(使用 Java 文档中的术语“对称地”)。

Java 文档表明类的equals() 方法必须遵循以下规则:

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

它是自反的:对于任何非空引用值xx.equals(x) 应该返回 true。 它是对称的:对于任何非空引用值 xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 应该返回 true。 它是可传递的:对于任何非空引用值xyz,如果x.equals(y) 返回true 并且y.equals(z) 返回true,那么x.equals(z) 应该返回true。李> 一致:对于任何非空引用值xyx.equals(y) 的多次调用始终返回 true 或始终返回 false,前提是没有修改对象上 equals 比较中使用的信息。 对于任何非空引用值xx.equals(null) 应返回 false。

【讨论】:

【参考方案4】:

仅仅因为您的Testequals 可能会在您将字符串传递给它时返回true,但这并不意味着Stringequals 在您将Test 实例传递给它时将永远返回true它。其实Stringequals只有在传递给它的实例是另一个String时才能返回true

public boolean equals(Object anObject) 
    if (this == anObject) 
        return true;
    
    if (anObject instanceof String)  // the passed instance must be a String
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) 
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) 
                if (v1[i] != v2[i])
                    return false;
                i++;
            
            return true;
        
    
    return false;

ArrayListcontains 调用indexOf,它使用搜索实例的equals 方法(您的示例中的String“a”),而不是List 的元素类型(其中在你的情况下是Test):

public int indexOf(Object o) 
    if (o == null) 
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
     else 
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i])) // o in your case is a String while
                                          // elementData[i] is a Test
                                          // so String's equals returns false
                return i;
    
    return -1;

【讨论】:

new Test("a").equals("a") 返回 true,而 "a".equals(new Test("a")) 返回 false,很好解释!谢谢伊兰! 啊,这很有意义。所以基本上解决这个问题的唯一方法(也是解决这个问题的唯一正确方法)是写test.contains(new Test("a"))? 我认为我们有o.equals(elementData[i] 而不是它的倒数的原因是因为我们有o != null 的保证,而object[] 没有这样的保证? @BenKnoble o 由于indexOf的具体实现,保证不为空。另一种可行的实现是有一个循环来检查每个elementData[i] 是否为空,如果elementData[i],equals(o) 不为空,则运行elementData[i],equals(o)。当o != null 时,该实现将需要更多的空检查,这可能是它未被选中的原因。

以上是关于List.contains() 失败,而 .equals() 工作的主要内容,如果未能解决你的问题,请参考以下文章

List 与 Set 的 contains方法比较

list.contains

令人惊讶的性能差异:List.Contains、Sorted List.ContainsKey、DataRowCollection.Contains、Data Table.Select、DataTab

list.contains

java: List.contains() 与手动搜索的性能差异

LINQ List.Contains() 重载以接受列名