【问题标题】:ArrayList contains method not work as I would expect? [duplicate]ArrayList 包含的方法不像我预期的那样工作? [复制]
【发布时间】:2017-08-10 14:40:15
【问题描述】:

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

如果此列表包含指定元素,则返回 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

【问题讨论】:

  • o 是传递给contains 的参数(在本例中为String),eList 的元素(您的类Foo)-@987654339 @ 不等于e.equals(o)
  • 在列表中查找实际的对象类型,而不是字符串。即在 FOO 列表中找到 FOO,而不是在 FOO 列表中找到 STRING
  • 为什么要在 equals() 方法中使用“@override”?您的类没有扩展任何其他类,我认为您的 equal() 就像您要声明的任何其他方法一样。
  • @ZiMtyth 没有。他从Object 类中覆盖它。强烈建议您添加此注释,以确保您拥有良好的签名
  • @nathan,谢谢你的澄清:)

标签: java arraylist


【解决方案1】:

您的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);
  }

【讨论】:

  • 谢谢我现在明白了
  • 不客气。我使用替代实现进行了编辑。
  • 这是关键答案。根本问题是 OP 对equal 的实现。不过不要觉得不好。这是Java中的常见错误
【解决方案2】:

你的问题是你的平等不是对称的。 Foo == String 并不暗示 String == Foo

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

o.equals(elementData[i])

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

【讨论】:

  • 感谢您的帮助
【解决方案3】:

查看文档here,包含将在字符串“item1”上使用 equals 方法,而不是在 item1 上使用 equals 方法。例如

"item1".equals(item1)

而不是

item1.equals("item1")

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

【讨论】:

  • 感谢您的帮助
【解决方案4】:

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

一般来说,您希望 equals 仅在您控制的类中为真(通常只在您正在比较的确切类中),这样您就可以确保关系是对称的。 (为避免进一步混淆,无论何时定义 equals,请始终定义 hashCode。)

【讨论】:

  • 谢谢我现在明白了
【解决方案5】:

您的 equals 实现显然不正确。

您应该阅读 Joshua Bloch 的“Effective Java”中的chapter 3,以了解如何正确覆盖等号和哈希码。

这样会更好:

/**
 * 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();
    }
}

【讨论】:

  • 感谢您的帮助,我确实尝试过,但现在两者都返回 false。不反对我
  • @Michael No. 例如,TO 的 equals 方法不是自反的(不确定这是否是术语):a.equals(b) 并不总是与 b.equals(a); 相同。这个默认值是它不起作用的原因之一
  • @Nathan Symmetric。有一个术语列表on the javadoc。反身将是x.equals(x) == true
  • 非常绝对的一个词。 Joshua Bloch 详细说明了如何正确地做到这一点。如果你不听从他的建议,那就错了。简单的。他的实现可以被证明是自反的、对称的和传递的。一切都是正确的。
  • @Michael 谢谢。另一方面,equals 方法 必须 也是自反的:p 至少它是预期的。打破任何这些标准的equals 的实现在定义上是错误,所以我认为@duffymo 的措辞是可以的。
【解决方案6】:

你的 Equals 方法没有考虑对象的实例,所以是错误的实现

您有一个 Foos 列表,并且您想知道该列表是否包含 STRING

这个:

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

不一样

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

因为

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

编辑:

包含在ArrayList中的实现方式为

    @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)

这和你做的不一样:

  Foo.equals(String) 

你打破了对称性的规则,这就是为什么不工作的原因!

【讨论】:

  • 我希望返回 true,因为我覆盖了 Foo 中的 equals 方法
  • new Foo("item1") 永远不会在等于字符串 "item1" 时返回 true,这只是不正确的。您可以编写一个 equals() 实现来做到这一点
  • @ΦX new Foo("item1") 永远不会在等于字符串“item1”时返回真 为什么不呢?如果我在Foo 类中覆盖我的equals 方法并使其始终返回true?您的意思可能是相反的:字符串“item1”永远不会在等于 new Foo("item1") 时返回 true
  • 感谢您的澄清
【解决方案7】:

问题在于您的 equals 覆盖:您正在比较 o.toString() 与 this.value。

要让它工作,这是你的选择:

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

【讨论】:

    猜你喜欢
    • 2012-06-13
    • 2018-11-19
    • 1970-01-01
    • 1970-01-01
    • 2014-10-14
    • 2014-03-16
    • 2016-03-07
    • 1970-01-01
    • 2014-05-16
    相关资源
    最近更新 更多