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

323

如果我创建一个对象并将其添加到我的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

10个回答

369

ArrayList 实现 了 List 接口。

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


63
如果您计划重写equals()方法,请确保同时重写hashcode()方法。如果您不这么做,使用集合时可能会出现意外情况。 - Mohd Farid
36
这是一个正确的答案,但请注意需要修改您的equals方法来接受一个“Object”而不是一个“Thing”。如果不这样做,您的equals方法将无法使用。 :) - mdierker
1
刚刚发现Eclipse在“源”菜单下有“生成hashCode()和equals()”选项。 - Volodymyr Krupach
1
这回答了标题中的问题,但并没有回答描述中的问题,即“如果我使用完全相同的构造函数输入创建另一个对象,contains()方法会将这两个对象评估为相同吗?” - robguinness
5
“集合”以优化的方式完成它们的工作,这意味着contains()首先检查两个对象的hashCode,然后才调用equals()。如果hashCode不同(对于两个不同的Thing实例总是如此),则不会调用equals()方法。一般来说,当您重写equals()方法时,不要忘记同时重写hashCode()方法。 - Sevastyan Savanyuk
显示剩余2条评论

57

我认为正确的实现应该是

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;
    }
}

1
if语句是不必要的。使用instanceof就足够了。 - Paul
@Paul 你在说哪个语句的哪一部分? - ChristopheCVB
7
object != null 这个条件不必要,因为 object instanceof Thing 同样检查对象是否为非空。 - Alexander Farber

14

ArrayList使用类(在您的情况下是Thing类)实现的equals方法来进行相等比较。


12

通常每次重写equals()方法时,您还应该重写hashCode()方法,即使只是为了提高性能。HashCode()方法决定进行比较时将对象放置在哪个“桶”中,因此任何两个返回trueequals()方法的对象应该返回相同的hashCode值。我无法记住hashCode()的默认行为(如果它返回0,则您的代码应该可以工作但速度较慢,但如果它返回地址,则您的代码将失败)。不过,我记得有很多次我的代码因为我忘记重写hashCode()而失败了。 :)


7

它使用对象的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;
    }
}

现在它可以工作了 ;)

6
在进行 Thing x = (Thing) o; 的操作之前,应该首先检查另一个对象是否为 null。 - steelshark

6

请注意,以下实现是错误的,当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;
    }
}

如何在消除重复的情况下实现这个? - Sujay

4
其他帖子已经回答了contains()如何工作的问题。你提出的同样重要的问题是如何正确实现equals()。而对于这个特定类的对象相等性是什么构成,答案真的取决于你想做什么。
如果你只关心对象相等性,那么equals()的默认实现(由Object提供)仅使用标识(即this == other)。如果这就是你想要的,那么就不要在你的类上实现equals()(让它从Object继承)。你编写的代码,虽然在寻求标识时有点正确,但在实际类中永远不会出现,因为它与使用默认的Object.equals()实现相比没有任何好处。
如果你刚开始学习这些东西,我强烈推荐Joshua Bloch的《Effective Java》一书。这是一本很棒的读物,涵盖了这种事情(以及在尝试进行基于标识之外的比较时如何正确地实现equals())。

为了我的目的,我试图查看ArrayList中是否有一个相等值的对象。我想这是一种hack方法。感谢您推荐的书籍。 - Mantas Vidutis

3

来自 JavaDoc 的快捷方式:

boolean contains(Object o)

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


2

record 覆盖 equals

您说:

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

… 和 …

假设构造函数对输入不进行任何有趣的操作,并且两个对象中存储的变量是相同的。

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

Java 16+ 中,record 特性会自动为您覆盖该方法。

记录是一种简洁的方式来编写一个主要用于透明和不可变地传达数据的类。默认情况下,您只需声明成员字段。编译器会隐式创建构造函数、getter方法、equals和hashCode方法以及toString方法。
equals方法的逻辑默认是比较一个对象的每个成员字段与同一类别的另一个对象中的对应字段。同样,hashCode和toString方法的默认实现也考虑每个成员字段。
record Thing( int amount ) {} ;

这就是全部代码,您可以拥有一个完全可用的只读类,而无需编写通常所需的样板代码
使用示例。
Thing x = new Thing( 100 ) ; 
Thing y = new Thing( 100 ) ; 
boolean parity = x.equals( y ) ;

当运行时。
``` parity = true ```
回到你的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 ```
奖励特性:可以在方法内部声明记录。或者像传统类一样,您可以将记录声明为嵌套类或单独的类。

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