ArrayList不使用重写的equals

时间:2011-07-06 13:01:06

标签: java arraylist override equals

我遇到了使ArrayList正确使用overriden equals的问题。问题是我正在尝试使用equals来测试单个键字段,并使用ArrayList.contains()来测试是否存在具有正确字段的对象。这是一个例子

public class TestClass  {
    private static class InnerClass{    
    private final String testKey;
    //data and such

    InnerClass(String testKey, int dataStuff) {
        this.testKey =testKey;
        //etc
    }
    @Override
    public boolean equals (Object in) {
        System.out.println("reached here");
        if(in == null) {
        return false;
        }else if( in instanceof String) {
        String inString = (String) in;
        return testKey == null ? false : testKey.equals(inString);
        }else {
        return false;
        }       
    }       
    }

    public static void main(String[] args) {    
    ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
    //add some entries
    objectList.add(new InnerClass("UNIQUE ID1", 42));
    System.out.println( objectList.contains("UNIQUE ID1")); 
    }    
}

令我担心的是,我不仅在输出上出错,而​​且我也没有得到“到达此处”输出。

有没有人有任何想法为什么这个覆盖被完全忽略?是否有一些我不知道的覆盖和内部类的微妙之处?

编辑: 有问题的网站所以我似乎无法标记答案。 感谢您的快速回复:是的,我认为它是字符串.equals thta被调用,而不是我自定义的。我想这是现​​在的老式支票

11 个答案:

答案 0 :(得分:16)

如果您查看ArrayList的来源,您会看到它调用其他对象的equals。在您的情况下,它会调用equals的{​​{1}},这将检查其他对象是否不是String "UNIQUE ID1"类型,只返回String

false

对于仅public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { ... for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; ... return -1; } contains InnerClass的案例通话id

objectList.contains(new InnerClass("UNIQUE ID1"))

不要忘记为仅equals进行比较的InnerClass实施id

答案 1 :(得分:7)

根据the JavaDoc of List.contains(o),定义为返回true

  

当且仅当此列表包含至少一个e元素(o==null ? e==null : o.equals(e))时。

请注意,此定义在equals上调用o参数 List中的元素}。

因此String.equals()将被调用,而不是InnerClass.equals()

另请注意,the contract for Object.equals()表示

  

对称:对于任何非空引用值xyx.equals(y)应当返回true当且仅当{ {1}}返回y.equals(x)

但是您违反了此约束,因为true会返回new TestClass("foo", 1).equals("foo"),但true将始终返回"foo".equals(new TestClass("foo", 1))

不幸的是,这意味着您的用例(可以与另一个标准类相同的自定义类)无法以完全符合的方式实现。

如果你仍然想做这样的事情,你必须仔细阅读所有集合类非常的规范(有时是实现)并检查对于像这样的陷阱。

答案 2 :(得分:3)

您使用contains而不是String的参数调用InnerClass

System.out.println( objectList.contains("UNIQUE ID1"))

在我的JDK中:

public class ArrayList {

    public boolean contains(Object o) {
    return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
    if (o == null) {
        // omitted for brevity - aix
    } else {
        for (int i = 0; i < size; i++)
        if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
            return i;
    }
    return -1;
    }
}

请注意indexOf调用o.equals()的方式。在您的情况下,oString,因此您的objectList.contains将使用String.equals而非InnerClass.equals

答案 3 :(得分:2)

通常,您还需要覆盖hashCode(),但这不是主要问题。您正在使用非对称equals(..)方法。文档清楚地表明它应该是对称的:

  

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

你所观察到的是由于合同破裂导致的意外行为。

创建一个实用程序方法,该方法迭代所有项目并使用字符串equals(..)进行验证:

public static boolean containsString(List<InnerClass> items, String str) {
    for (InnerClass item : items) {
        if (item.getTestKey().equals(str)) {
           return true;
        }
    }
    return false;
} 

你可以用番石榴的Iterables.any(..)方法做类似的事情:

final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
   @Override
   public boolean apply(InnerClass input){ 
       return input.getTestKey().equals(str);
   }
}

答案 4 :(得分:1)

你的equals实现是错误的。您的in参数不应为String。它应该是InnerClass

public boolean equals(Object o) {
  if (this == o) return true;
  if (!(o instanceof InnerClass) return false;
  InnerClass that = (InnerClass)o;
  // check for null keys if you need to
  return this.testKey.equals(that.testKey);
}

(注意instanceof null返回false,因此您不需要先检查null。

然后,您将使用以下命令测试列表中是否存在等效对象:

objectList.contains(new InnerClass("UNIQUE ID1"));

但是如果你真的想通过String键检查InnerClass,为什么不使用Map<String,InnerClass>呢?

答案 5 :(得分:0)

虽然没有回答您的问题,但许多收藏集使用hashcode()。您也应该覆盖它以“同意”equals()

实际上,您应该始终同时实施equalshashcode,并且它们应始终保持一致。作为Object.equals()状态的javadoc:

  

请注意,通常需要   每当覆盖hashCode方法   这个方法被覆盖,以便   保持一般合同   hashCode方法,表明了这一点   等于对象必须具有相等的哈希值   码。

具体来说,很多收藏都依赖于这个合同得到维护 - 否则行为是不确定的。

答案 6 :(得分:0)

您的代码存在一些问题。 我的建议是,如果您不熟悉它,则应避免完全取代等等,并将其扩展为新的实现,如此......

class MyCustomArrayList extends ArrayList<InnerClass>{

    public boolean containsString(String value){
        for(InnerClass item : this){
            if (item.getString().equals(value){
                return true;
            }
        }
        return false;
    }

}

然后你可以做类似

的事情
List myList = new MyCustomArrayList()
myList.containsString("some string");

我建议这样做,因为如果你覆盖equals也应该覆盖hashCode,看起来你在这方面缺乏一点知识 - 所以我会避免它。

此外,contains方法调用equals方法,这就是您看到“到达此处”的原因。再次,如果你不理解呼叫流程,我会避免它。

答案 7 :(得分:0)

另一方面,如果您按如下方式更改代码,则会调用您的equal方法。希望这能清除这个概念。

package com.test;

import java.util.ArrayList;    
import java.util.List;

public class TestClass  {
    private static class InnerClass{    
        private final String testKey;
        //data and such

        InnerClass(String testKey, int dataStuff) {
            this.testKey =testKey;
            //etc
        }

        @Override
        public boolean equals (Object in1) {
            System.out.println("reached here");
            if(in1 == null) {
                return false;
            }else if( in1 instanceof InnerClass) {
                return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
            }else {
                return false;
            }       
        }       
    }

    public static void main(String[] args) {    
        ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
        InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
        InnerClass in2 = new InnerClass("UNIQUE ID1", 42);

        //add some entries
        objectList.add(in1);
        System.out.println( objectList.contains(in2)); 
    }    
}

答案 8 :(得分:0)

正如很多帖子所说,问题是list.indexOf(obj)函数调用obj的“equals”,而不是列表中的项目。

我有同样的问题,“contains()”不满足我,因为我需要知道元素在哪里!我的方法是创建一个只有要比较的参数的空元素,然后调用indexOf。

实现这样的功能,

public static InnerClass empty(String testKey) {
    InnerClass in = new InnerClass();
    in.testKey =testKey;
    return in;
}

然后,像这样调用indexOf:

ind position = list.indexOf(InnerClass.empty(key));

答案 9 :(得分:0)

您的代码中有两个错误。

第一: 在“objectList”对象上调用的“contains”方法应该传递一个新的InnerClass对象作为参数。

第二: equals方法(应该接受参数作为Object,并且是正确的)应该根据接收的对象正确处理代码。  像这样:

@Override
    public boolean equals (Object in) {
        System.out.println("reached here");
        if(in == null) {
        return false;
        }else if( in instanceof InnerClass) {
        String inString = ((InnerClass)in).testKey;
        return testKey == null ? false : testKey.equals(inString);
        }else {
        return false;
        }       
    }  

答案 10 :(得分:0)

这篇文章是在Java 8可用之前编写的,但是现在它是2017而不是使用List.containts(...)方法,你可以像这样使用新的Java 8方式:

System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());

并为您的TestClass提供testKey字段的getter:

public String getTestKey() {

   return testKey;
}

这种方法的好处是你不必修改equals或hash方法,你就会对同龄人看起来像老板!

相关问题