我遇到了使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被调用,而不是我自定义的。我想这是现在的老式支票
答案 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()
表示
对称:对于任何非空引用值
x
和y
,x.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()
的方式。在您的情况下,o
是String
,因此您的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()
。
实际上,您应该始终同时实施equals
和hashcode
,并且它们应始终保持一致。作为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方法,你就会对同龄人看起来像老板!