繁体   English   中英

ArrayList 不使用覆盖的等于

[英]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仅包含idInnerClass

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

不要忘记为仅比较idInnerClass实现equals

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

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

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

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

另请注意, Object.equals()的合同规定

它是对称的:对于任何非空引用值xy ,当且仅当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() 在您的情况下, oString ,因此您的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()

实际上,您应该始终同时实现equalshashcode ,并且它们应该始终保持一致。 正如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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM