ArrayList 的 contains() 方法如何评估对象?

Posted

技术标签:

【中文标题】ArrayList 的 contains() 方法如何评估对象?【英文标题】:How does a ArrayList's contains() method evaluate objects? 【发布时间】:2011-02-08 05:06:52 【问题描述】:

假设我创建了一个对象并将其添加到我的ArrayList。如果我然后创建另一个具有完全相同的构造函数输入的对象,contains() 方法会将这两个对象评估为相同吗?假设构造函数没有对输入做任何有趣的事情,并且存储在两个对象中的变量是相同的。

ArrayList<Thing> basket = new ArrayList<Thing>();  
Thing thing = new Thing(100);  
basket.add(thing);  
Thing another = new Thing(100);  
basket.contains(another); // true or false?

class Thing   
    public int value;  

    public Thing (int x) 
        value = x;
    

    equals (Thing x) 
        if (x.value == value) return true;
        return false;
    

这是应该如何实现class 以使contains() 返回true

【问题讨论】:

【参考方案1】:

ArrayList implements 列表接口。

如果您查看contains 方法中的Javadoc for List,您会发现它使用equals() 方法来评估两个对象是否相同。

【讨论】:

万一你打算重写equals(),确保你也重写了hashcode()方法。如果您不这样做,使用 Collections 时可能无法按预期工作? 这是一个正确的答案,但请注意,您需要更改您的 equals 方法以接受 Object 而不是 Thing。如果你不这样做,你的 equals 方法将不会被使用。 :) 刚刚自己发现eclipse在Source菜单下有“Generate hashCode() and equals”。 这回答了标题中的问题,但没有回答描述中的问题,即“如果我然后创建另一个具有完全相同的构造函数输入的对象,contains() 方法是否会将这两个对象评估为一样吗?” Collections 以优化的方式做他们的事情,这意味着contains() 首先检查两个对象的hashCodes,然后才调用equals()。如果hashCodes 不同(Thing 的两个不同实例总是如此),则不会调用equals() 方法。根据经验,当您覆盖 equals() 时,您不应该忘记也覆盖 hashCode()【参考方案2】:

我认为正确的实现应该是

public class Thing

    public int value;  

    public Thing (int x)
    
        this.value = x;
    

    @Override
    public boolean equals(Object object)
    
        boolean sameSame = false;

        if (object != null && object instanceof Thing)
        
            sameSame = this.value == ((Thing) object).value;
        

        return sameSame;
    

【讨论】:

if 声明是不必要的。 instanceof 就够了。 @Paul 你说的是声明的哪一部分? object != null 条件是不必要的,因为object instanceof Thing 也会检查对象是否不为空。【参考方案3】:

ArrayList 使用类(您的案例 Thing 类)中实现的 equals 方法来进行等值比较。

【讨论】:

【参考方案4】:

通常,您还应该在每次覆盖 equals() 时覆盖 hashCode(),即使只是为了提高性能。 HashCode() 决定在进行比较时您的对象被分类到哪个“桶”中,因此equal() 评估为真的任何两个对象都应该返回相同的hashCodevalue()。我不记得hashCode() 的默认行为(如果它返回 0,那么您的代码应该可以工作但速度很慢,但如果它返回地址,那么您的代码将失败)。我确实记得很多时候我的代码失败了,因为我忘了覆盖hashCode()。 :)

【讨论】:

【参考方案5】:

它对对象使用 equals 方法。因此,除非 Thing 覆盖 equals 并使用存储在对象中的变量进行比较,否则它不会在 contains() 方法上返回 true。

【讨论】:

【参考方案6】:
class Thing   
    public int value;  

    public Thing (int x) 
        value = x;
    

    equals (Thing x) 
        if (x.value == value) return true;
        return false;
    

你必须写:

class Thing   
    public int value;  

    public Thing (int x) 
        value = x;
    

    public boolean equals (Object o) 
    Thing x = (Thing) o;
        if (x.value == value) return true;
        return false;
    

现在可以了 ;)

【讨论】:

你不应该做 Thing x = (Thing) o;没有先检查另一个对象是否为空【参考方案7】:

只是想注意,当value 不是原始类型时,以下实现是错误的:

public class Thing

    public Object value;  

    public Thing (Object x)
    
        this.value = x;
    

    @Override
    public boolean equals(Object object)
    
        boolean sameSame = false;

        if (object != null && object instanceof Thing)
        
            sameSame = this.value == ((Thing) object).value;
        

        return sameSame;
    

在这种情况下,我提出以下建议:

public class Thing 
    public Object value;  

    public Thing (Object x) 
        value = x;
    

    @Override
    public boolean equals(Object object) 

        if (object != null && object instanceof Thing) 
            Thing thing = (Thing) object;
            if (value == null) 
                return (thing.value == null);
            
            else 
                return value.equals(thing.value);
            
        

        return false;
    

【讨论】:

如何实现这个同时消除重复?【参考方案8】:

其他海报已经解决了有关 contains() 工作原理的问题。

您问题的一个同样重要的方面是如何正确实现 equals()。这个问题的答案实际上取决于什么构成了这个特定类的对象相等。在您提供的示例中,如果您有两个 x=5 的不同对象,它们是否相等?这真的取决于你想要做什么。

如果您只对对象相等性感兴趣,那么 .equals() 的 default 实现(由 Object 提供)仅使用标识(即 this == other)。如果这就是你想要的,那么就不要在你的类上实现 equals() (让它从 Object 继承)。您编写的代码,虽然如果您要进行身份验证的话是正确的,但永远不会出现在真正的 b/c 类中,与使用默认的 Object.equals() 实现相比,它没有任何好处。

如果您刚开始接触这些东西,我强烈推荐 Joshua Bloch 的 Effective Java 书。这是一本很好的读物,涵盖了这类事情(以及当您尝试做的不仅仅是基于身份的比较时,如何正确实现 equals())

【讨论】:

出于我的目的,我试图查看 ArrayList 中是否存在相等值的对象。我想这是一种黑客行为。谢谢推荐书【参考方案9】:

JavaDoc的快捷方式:

布尔 包含(对象o)

如果此列表包含指定元素,则返回 true。更正式地说, 当且仅当此列表包含至少一个元素 e 时才返回 true 那 (o==null ? e==null : o.equals(e))

【讨论】:

【参考方案10】:

record 覆盖 equals

你说:

另一个具有完全相同的构造函数输入的对象

……和……

假设构造函数没有对输入做任何有趣的事情,并且存储在两个对象中的变量是相同的。

正如其他答案所解释的,您必须重写 Object#equals 方法才能使 List#contains 工作。

在Java 16+ 中,record 功能会自动为您覆盖该方法。

记录是编写类的一种简短方式,其主要目的是透明且不可变地传递数据。默认情况下,您只需声明成员字段。编译器隐式创建构造函数、getter、equals & hashCodetoString

equals 默认的逻辑是将一个对象的每个成员字段与同一类的另一个对象中的对应部分进行比较。同样,hashCodetoString 方法的默认实现也会考虑每个成员字段。

record Thing( int amount )  ;

就是这样,这就是一个功能齐全的只读类所需的所有代码,没有通常的boilerplate code。

示例用法。

Thing x = new Thing( 100 ) ; 
Thing y = new Thing( 100 ) ; 
boolean parity = x.equals( y ) ;

运行时。

奇偶校验 = 真

回到您的List#contains 问题。

Thing x = new Thing( 100 );
List < Thing > things =
        List.of(
                new Thing( 100 ) ,
                new Thing( 200 ) ,
                new Thing( 300 )
        );

boolean foundX = things.contains( x );

运行时。

foundX = true


额外功能:可以在方法内本地声明记录。或者像传统类一样,您可以将记录声明为嵌套类或单独的类。

【讨论】:

以上是关于ArrayList 的 contains() 方法如何评估对象?的主要内容,如果未能解决你的问题,请参考以下文章

ArrayList 的自定义 Contains 方法

ArrayList 的 contains() 方法如何评估对象?

ArrayList.contains() 方法实现方式

Arraylist中contains方法底层实现解读与HashSet自定义类型去重效果的简述(接上一篇)

慎用ArrayList的contains方法,使用HashSet的contains方法代替

[Java]ArrayList集合的contains方法