ArrayList包含的方法不能像我期望的那样工作?

时间:2017-08-10 14:40:16

标签: java arraylist

ArrayList在其equals()方法中使用contains来查看提供对象是否等于列表中的任何项目,如文档所示:

  

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

我有这个班级

class Foo
{
  private String value;
  public Foo(String value)
  {
    this.value = value;
  }

  @Override
  public boolean equals(Object o)
  {
    return o == null ? this.value == null : o.toString().equals(this.value);
  }
}

我想使用contains方法检查项目是否像这样存在

List<Foo> list = new ArrayList<Foo>();

Foo item1 = new Foo("item1");
Foo item2 = new Foo("item2");
list.add(item1);
list.add(item2);

System.out.println(item1.equals("item1"));       //return true
System.out.println(list.contains("item1"));      //false !! why?!

contains方法返回false,而item1.equals("item1")返回true

contains使用equals方法提供对象

时返回false的原因

7 个答案:

答案 0 :(得分:6)

您的equals()实施违反了对称原则:

  

它是对称的:对于任何非空参考值x和y,   当且仅当y.equals(x)返回时,x.equals(y)才应返回true   真。

item1.equals("item1")返回true

,而

"item1".equals(item1)返回false

因此,您不应期望Collection方法(例如contains())以一致的方式运作。

作为一般规则,equals()覆盖不应该尝试与其他类互操作,而只应与基础类的实例互操作。

在您的情况下,它仅适用于传递给方法的String参数 您应该使其适用于Foo实例参数:

  @Override
  public boolean equals(Object o) {
     if (!(o instanceof Foo){ 
       return false;
     }
     Foo other = (Foo) o;
     return Objects.equals(other.value, this.value);
  }

答案 1 :(得分:5)

查看文档here包含将在字符串&#34; item1&#34;上使用equals方法。而不是item1上的equals方法。 e.g。

"item1".equals(item1)

而不是

item1.equals("item1")

您可以改用list.contains(new Foo("item1"))

答案 2 :(得分:5)

你的问题是你的平等不对称。 Foo == String并不意味着String == Foo

如果您查看ArrayList.contains的实施,您会看到它调用objectToFind.equals(objectInList),这可能与您的预期相反:

o.equals(elementData[i])

所以在你的情况下是String.equals(Foo)。因为String.equals将为非String的任何内容返回false,ArrayList.contains将返回false。

答案 3 :(得分:4)

item1.equals("item1")为真,"item1".equals(item1)将为false。具有非对称等于关系会导致很多混淆。

通常,您希望equals仅在您控制的类中成为true(通常只是您要比较的确切类),以便您可以确保关系是对称的。 (为了避免进一步混淆,只要定义hashCode,就要定义equals。)

答案 4 :(得分:2)

你的Equals方法没有考虑Object的实例,所以错误地实现了

你有一个Foos列表,你想知道列表是否包含STRING

这样:

OKHttp client = ....
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
client.interceptors().add(interceptor);

Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("url")
            .client(client) // add custom OkHttp client

不同
System.out.println(list.contains("item1"));  

因为

System.out.println(list.contains(new Foo("item1")));  

编辑:

包含在ArrayList中实现为

 new Foo("item1") never ever will return true on equals to the string "item1"

所以这部分是重要的一部分

    @Override
    public int indexOf(Object o) {
        E[] a = this.a;
        if (o == null) {
            for (int i = 0; i < a.length; i++)
                if (a[i] == null)
                    return i;
        } else {
            for (int i = 0; i < a.length; i++)
                if (o.equals(a[i]))
                    return i;
        }
        return -1;
    }

你可以看到

for (int i = 0; i < a.length; i++)
         if (o.equals(a[i]))
                  return i;

表示

  o.equals(a[i])

与你所做的不同:

  String.equals(Foo)

你违反了对称的规则,这就是为什么不起作用的原因!

答案 5 :(得分:2)

您的平等实施显然不正确。

你应该阅读Joshua Bloch的“Effective Java”的chapter 3来学习如何正确地覆盖equals和hashcode。

这会更好用:

/**
 * Demo equals override
 * User: mduffy
 * Date: 8/10/2017
 * Time: 10:45 AM
 * @link https://stackoverflow.com/questions/45616794/arraylist-contains-method-not-work-as-i-would-except
 */
public class Foo {

    private final String value;

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Foo)) return false;

        Foo foo = (Foo) o;

        return value != null ? value.equals(foo.value) : foo.value == null;
    }

    @Override
    public int hashCode() {
        return value != null ? value.hashCode() : 0;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Foo{");
        sb.append("value='").append(value).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

答案 6 :(得分:1)

问题在于你的等于覆盖:你正在比较o.toString()和this.value。

让它运作这是你的选择:

  1. 覆盖Foo类上的toString方法以返回value
  2. 将最后一部分更改为this.value.equals(o.getValue()) //必须创建getter