ArrayList's contains()方法如何计算对象?

假设我创建了一个对象并将其添加到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吗?

585619 次浏览

ArrayList使用在类(你的case Thing类)中实现的equals方法来进行相等比较。

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

ArrayList implements列表接口。

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

其他帖子已经解决了contains()如何工作的问题。

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

如果你只对对象相等感兴趣,那么.equals()的默认的实现(object提供的)只使用标识符(即this == other)。如果这是你想要的,那么就不要在你的类上实现equals()(让它从Object继承)。你写的代码,虽然是正确的,如果你是为了标识,永远不会出现在一个真正的类b/c,它提供没有使用默认的Object.equals()实现的好处。

如果您刚刚开始学习这些东西,我强烈推荐Joshua Bloch的《Effective Java》一书。这是一本很棒的读物,涵盖了这类内容(以及当您试图做更多基于标识的比较时如何正确实现equals())

通常,在每次覆盖equals()时也应该覆盖hashCode(),即使只是为了提高性能。HashCode()决定在进行比较时将对象排序到哪个“bucket”中,因此equal()计算为true的任何两个对象都应该返回相同的hashCode value()。我不记得hashCode()的默认行为(如果它返回0,那么你的代码应该工作,但缓慢,但如果它返回地址,那么你的代码将失败)。我确实记得我的代码失败了很多次,因为我忘记覆盖hashCode()。:)

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

现在它工作了;)

我认为正确的实现应该是

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

只是想指出,当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;
}
}

JavaDoc的快捷方式:

布尔 包含对象(o)

如果列表包含指定元素则返回true。更正式, 当且仅当此列表包含至少一个元素e such时返回true (o = =零?E ==null: o. = (E)) < / p >

record覆盖equals

你说:

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

还有……

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

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

Java 16+中,记录特性自动为你覆盖该方法。

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

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

record Thing( int amount ) {} ;

就是这样,这就是一个功能完整的只读类所需的所有代码,没有任何常见的样板代码

使用例子。

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

运行时。

奇偶校验= 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


附加特性:可以在方法中本地声明一条记录。或者像传统的类一样,可以将记录声明为嵌套类或单独的类。