2011-07-06 56 views
10

我遇到了使ArrayList正确使用重写equals的问题。问题是我试图使用equals来仅测试单个键字段,并使用ArrayList.contains()来测试是否存在具有正确字段的对象。下面是一个例子ArrayList未使用重写的等于

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

我担心的是,我不仅在输出越来越假,但我还没有得到“到达这里”输出。

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

编辑: 有问题的网站,所以我似乎无法标记答案。 感谢您的快速回应:是我的一个疏忽,它是String .equals thta被称为,而不是我自定义的一个。我猜这是现在的老式检查

回答

15

如果您检查ArrayList的来源,您会看到它调用equals其他对象。在你的情况下,它会调用String "UNIQUE ID1"equals将检查其他对象是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; 
} 

对于您的情况下调用containsInnerClass只包含id

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

不要忘记实施equalsInnerClass,它只比较id

+0

谢谢。关于新的InnerClass()的建议也很有帮助。 :) – Sufian

+1

在我看来indexOf的实施应该是相反的方式:elementData [i] .equals(o)... – marcolopes

0

虽然不回答你的问题,许多收藏品使用hashcode()。您也应该覆盖它以与equals()“同意”。

其实,你应该始终实施equalshashcode在一起,他们应该永远是相互一致。至于的Javadoc Object.equals()状态:

注意,它通常是必要的 覆盖每当 这种方法被重写hashCode方法,以 保持对 hashCode方法,其中指出, 总承包合同相同的对象必须具有相同的散列码 代码。

具体来说,许多集合依赖于这个合同被维护 - 行为是未定的,否则。

+1

尽管确实应该实现两者,但在这种情况下它并没有帮助,因为'ArrayList'不是基于散列的:它并不关心hashCode()'实现。 –

+0

'ArrayList'不使用'hashCode' –

+0

好。我“凝聚”了我的答案。好点你们俩 – Bohemian

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

7

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

当且仅当该列表中包含的至少一种元素e使得(o==null ? e==null : o.equals(e))

注意,这个定义呼吁oequals,这是参数那就是在List的元素。

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

还要注意的是the contract for Object.equals()指出

这是对称:对于任何非空引用值xyx.equals(y)应该返回true当且仅当y.equals(x)回报true

但你违反此约束,因为new TestClass("foo", 1).equals("foo")回报true"foo".equals(new TestClass("foo", 1))总是返回false

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

如果你仍然想要做这样的事情,你必须阅读你的所有集合类非常仔细的规范(有时是执行),并检查是否有缺陷,如这一点。

+0

工作时间太长。当你开始忘记基本知识或误读api规范时,从来没有一个好迹象。我发现有一个新的InnerClass(String testKey)构造函数来生成一个测试对象,但实际数据为null/0的解决方法。 –

2

通常,您还需要覆盖hashCode(),但这不是主要问题。你有一个不对称的方法。文档清楚地表明它应该是对称的:

它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals )返回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); 
    } 
} 
1

你平等的实现是错误的。你的参数不应该是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,所以你不需要检查空第一)。

你会再测试在您的列表中相当于对象存在使用:

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

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

+0

直截了当地说,自从我上一次使用Java已经过去了大约6年之后,虽然我有编码者的思维方式,但我缺乏经验。自从我最初学习C以来,我还不完全熟悉Maps或Java哈希。现在使用Java是因为对于小型应用程序/工具开发而言,速度要快得多,我需要体面的国际化和平台独立性。我添加了一个构造函数(以及用于实现抽象父类,姐妹类的其他一些类),以仅使用测试密钥创建测试对象。 –

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,似乎你缺乏这方面的小知识 - 所以我只想避免。

此外,包含方法调用equals方法这就是为什么你看到的“到达这里”。同样,如果你不了解通话流程,我会避免它。

+0

我的确了解了调用流程,但错过了它使用String.equals而不是InnerClass.equals。由于ArrayList不是基于散列的,我没有必要在代码片段中包含hashCode。当然,我包含一个hashCode覆盖,即使它在这里需要的所有东西都是返回testKey.hashCode() –

0

在其他的方式,您等于方法被,如果你改变你的代码如下调用。希望这清除了这个概念。

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

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

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

实现这样的功能,

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

,然后调用的indexOf像这样:

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

有在你的代码的两个错误。

第一: “包含”要求“链表类”对象应该传递一个新的对象将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; 
     }  
    } 
0

这个职位是第一次编写的Java 8是可用的,但现在,它是2017年而不是使用List.containts(...)方法,你可以这样使用新的Java 8路前:

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

,给你的TestClass一个getter你密押领域:

public String getTestKey() { 

    return testKey; 
} 

这种方法的好处是,你不必修改等号或哈希方法和you'l我看起来像你的同龄人的老板!