[英]ArrayList not using the overridden equals
我在让 ArrayList 正确使用覆盖的等于时遇到问题。 问题是我正在尝试使用等于仅测试单个关键字段,并使用 ArrayList.contains() 来测试是否存在具有正确字段的 object。 这是一个例子
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"));
}
}
让我担心的是,我不仅在 output 上弄错了,而且我也没有得到“到达这里”的 output。
有谁知道为什么这个覆盖被完全忽略? 我不知道的覆盖和内部类是否有一些微妙之处?
编辑:网站有问题,所以我似乎无法标记已回答。 感谢您的快速回复:是的,我的疏忽是调用了 String.equals thta,而不是我的自定义。 我想现在是老式的支票
如果您检查ArrayList
的来源,您会看到它调用了其他 object 的equals
。 在您的情况下,它将调用String "UNIQUE ID1"
的equals
,它将检查其他 object 不是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
仅包含id
的InnerClass
:
objectList.contains(new InnerClass("UNIQUE ID1"))
不要忘记为仅比较id
的InnerClass
实现equals
。
根据List.contains(o)
的JavaDoc ,它被定义为返回true
当且仅当此列表包含至少一个元素
e
使得(o==null? e==null: o.equals(e))
。
请注意,此定义在o
上调用equals
,它是参数而不是List
中的元素。
因此String.equals()
将被调用,而不是InnerClass.equals()
。
另请注意, Object.equals()
的合同规定
它是对称的:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应该返回true
。
但是您违反了此约束,因为new TestClass("foo", 1).equals("foo")
返回true
但"foo".equals(new TestClass("foo", 1))
将始终返回false
。
不幸的是,这意味着您的用例(可以等于另一个标准类的自定义 class)无法以完全一致的方式实现。
如果你仍然想做这样的事情,你必须非常仔细地阅读所有集合类的规范(有时是实现),并检查诸如此类的陷阱。
您正在调用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
。
通常,您还需要覆盖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);
}
}
您的 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)。
然后,您将使用以下方法测试列表中是否存在等效的 object:
objectList.contains(new InnerClass("UNIQUE ID1"));
但是如果你真的想通过 String 键检查 InnerClass,为什么不使用Map<String,InnerClass>
呢?
另一方面,如果您按如下方式更改代码,则会调用您的 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));
}
}
正如许多帖子所说,问题在于 list.indexOf(obj) function 调用 obj 的“等于”,而不是列表中的项目。
我遇到了同样的问题,“contains()”并没有让我满意,因为我需要知道元素在哪里。我的方法是创建一个空元素,只使用要比较的参数。 然后调用 indexOf。
像这样实现 function,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
然后,像这样调用 indexOf :
ind position = list.indexOf(InnerClass.empty(key));
您的代码中有两个错误。
首先:在“objectList”object 上调用的“contains”方法应该传递一个新的 InnerClass object 作为参数。
第二:equals方法(应接受参数为Object,正确)应根据收到的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;
}
}
这篇文章最初是在 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;
}
这种方法的好处是您不必修改 equals 或 hash 方法,您将在同行中看起来像个老板!
尽管没有回答您的问题,但许多 Collections 使用hashcode()
。 您也应该覆盖它以“同意” equals()
。
实际上,您应该始终同时实现equals
和hashcode
,并且它们应该始终保持一致。 正如Object.equals()
的 javadoc 所述:
请注意,每当重写该方法时,通常都需要重写 hashCode 方法,以维护 hashCode 方法的一般约定,即相等的对象必须具有相等的 hash 代码。
具体来说,许多 Collections 依赖于维护此合同 - 否则行为未定义。
您的代码存在一些问题。 我的建议是避免完全覆盖等号,如果您不熟悉它并将其扩展为像这样的新实现......
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 方法,这就是您看到“到达此处”的原因。 同样,如果您不了解呼叫流程,我会避免它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.