【问题标题】:Overriding Object#equals(Object) outside the class在类外覆盖 Object#equals(Object)
【发布时间】:2016-07-12 01:14:00
【问题描述】:

使用list.contains(someObject)时是否可以在本地覆盖Object#equals(Object)?

示例:

class SomeObject {
    ...
    private int id;
    private String name;

    @Overrdide
    public boolean equals(Object other){
         ...
         return this.id == other.id;
    }
}

但是,如果我在使用list.contains(someObject) 时想要另一种等于怎么办?例如,我想知道列表是否包含某个名称?是否可以“匿名”覆盖Object#equals(Object)?

更具体的解释为什么我需要它:

int objectId = ... // Some event which passes me the attribute of an object but not the object itself

现在我有了List<SomeObject> someObjects,我想知道这个列表是否包含一个带有objectId 的对象,而不必对其进行迭代。

我能想到的一个“解决方案”是使用Map<Integer, SomeObject> mapping,然后使用someObject = mapping.get(objectId)

编辑:我的问题不是重复的,因为我特别要求覆盖Object#equals(Object)。

【问题讨论】:

  • 应该是这样,但恕我直言,这是集合 API 最糟糕的事情。您可以在许多地方使用自定义 Comparator,但您只能使用 equals 的一个概念。
  • 如果 Java 只有 case-classes :-(
  • 使用比较器指定相等性适用于 Set 和 Map 键(但它会损害性能 - 操作变为 O(log n),因为您必须使用 TreeMap 而不是 HashMap)。但它对 List 没有帮助。应该可以通过 indexOf、contains 等方法使用自定义的相等概念,但如果不创建包装类并根据需要覆盖 equals 是不可能的。这很烦人。
  • @SklogW 列表有多大?如果您对顺序不感兴趣并且多次查询一个属性,您可以使用特定于该属性的比较器重新排序列表,然后检查Collections.binarySearch(list, cmp) >= 0
  • 不是重复的,因为我专门询问是否覆盖 equals()。恕我直言,重复是隐含的。

标签: java equals


【解决方案1】:

您可以对从列表中获取的流对象应用过滤器。过滤器采用一个谓词,您可以在其中设置条件。然后可以检查过滤后的流是否为空:

list.stream().filter(e -> e.name.equals(otherName)).findAny().isPresent()

您可以使其可重复使用,例如:

private <T> boolean containsWithPredicate(List<T> list, Predicate<T> predicate) {
    return list.stream().filter(predicate).findAny().isPresent();
}

并按如下方式调用它:

boolean containsElement = containsWithPredicate(myListOfObjects,
                                                 (e -> e.name.equals(otherName)));

编辑:

通过调用Stream.anyMatch 而不是调用filter 然后findAny,有一种更简单的方法来执行上述完全相同的操作:

list.stream().anyMatch(predicate);

【讨论】:

  • 不确定这是解决方案还是解决方法,因为我还可以使用 for-each 循环进行迭代。但这绝对是一种方法。
  • @SklogW 我认为这是一个解决方案。需要一个可以自定义的“equals”匿名方法,因此首先想到的是函数式编程(您可以将过滤器的谓词作为参数传递)。它似乎也更易于阅读。
  • @SklogW 这就是你真正想要做的。您不想创建一个新的 equals 方法,而是要求检查列表是否包含基于特定参数的内容,为此您需要一个 Predicate - 这实际上是 @987654323 中的 lambda @方法是。
  • 特别是因为一个新的等号意味着我改变了一个对象的身份。你的方法清楚地表明了我的意图。谢谢!
  • 我有点惊讶没有 findAny 接受谓词。 LINQ 中的等价物是Any,它有两个重载。一个接受谓词,另一个不接受。这样您就不必将Where 和Any(或filter 和findAny)作为两个不同的操作。
【解决方案2】:

您可以使用 TreeSet 和自定义 Comparator 来实现您想要的。它不使用equals() 来表示相等,而是认为如果compare() 返回0,则对象是相等的。

假设我们希望所有Strings 的长度相同:

TreeSet<String> set = new TreeSet(new Comparator<>() {
    public int compare(String o1, String o2) {
       return o1.length() - o2.length();
    }
}
set.add("asd");
set.contains("foo");    // Returns true

如果您需要在不同时间对对象有不同的“相等定义”,同时仍然使用集合,这可能是一个有用的构造(也适用于 TreeMap)。

【讨论】:

  • 但它仍然给了我一个列表,其中包含一个永久定义什么相等或不相等的列表,对吧?如果我只想要某种 equals 一次怎么办?例如,我可以更改比较器吗?
  • 它将为该集合定义“什么是平等的”。您可以编写Comparator,使其对不同的对象具有不同的逻辑,但它会变得更容易出错,您需要确保遵守compare() 的合同。
【解决方案3】:

没有办法让同一个类有多个equals。但是您可以子类化您的类并覆盖hashCode() 和equals(),然后根据您要使用的equals 使用所需的实例。

我必须说我不喜欢那样做,equals 方法应该定义好,在不同的场景中使用多个equals 会很混乱。如果我需要两个不同的equals 方法,我将拥有完全不同的实现,而不是子类化。所以我强烈建议你重新考虑你的设计。

【讨论】:

  • 好建议。如果我需要不同的平等,那么也许我的设计就是问题所在。我会考虑的。
  • @SklogW 这是个好主意。想想其他可能使用你的程序的程序员,他们怎么知道选择什么实现? equals 应该很好定义。
  • 有道理。我想我正在努力解决一个糟糕的设计。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-05
  • 2012-10-11
  • 1970-01-01
  • 2011-02-27
  • 2013-08-09
  • 2015-01-14
  • 2010-09-23
相关资源
最近更新 更多