Java - 对象列表去重

36

我有一个对象列表/集合,这些对象可能具有相同的属性值,也可能没有。什么是获取具有相等属性的不同对象列表的最简单方法?是否有一种最适合此目的的集合类型?例如,在C#中,我可以使用LINQ执行以下操作:

var recipients = (from recipient in recipientList
                 select recipient).Distinct();

我最初的想法是使用lambdaj(链接文字),但它似乎不支持此功能。


1
使用这个类似于LINQ的库(github.com/nicholas22/jpropel-light),然后执行List<Recipient> recipientList; recipientList.distinct(); 这正是你发布的代码所做的。 - NT_
9个回答

50
return new ArrayList(new HashSet(recipients));

2
你实际上回答了问题(而不仅仅是列出回答问题的工具)。 - orbfish
哈,没想到可以使用这个快速的“技巧”来获取唯一的项目列表 :) - Bogdan
这是最好的答案..谢谢 :) - Kh.Taheri

33
使用接口Set的实现(类T可能需要自定义.equals()方法,您可能需要自己实现该.equals())。通常情况下,HashSet可以直接使用:它使用Object.hashCode()和Object.equals()方法来比较对象。对于简单对象,这应该足够唯一。如果不是,则必须相应地实现T.equals()和T.hashCode()。
请参见Gaurav Saini在下面的评论中提供的库以帮助实现equals和hashcode。

2
HashSet在哈希冲突时也会使用equals方法。 - Steve Kuo
1
这是不正确的 - Object.hashCode() 检查的是身份,而不是有意义的相等性。对于两个有意义相等的不同引用对象,Object.hashCode() 将返回 false。始终为将用作集合中的元素或映射键的对象实现 hashCode() 和 equals() 方法。 - Robert Munteanu
不太准确。让我给你举个例子:如果hashCode()返回1(完全合法),那么这将导致哈希冲突,因此HashSet将调用equals(实际上由HashMap支持)。 - Steve Kuo
3
或者,使用Apache Commons提供的HashCodeBuilder和EqualsBuilder来覆盖hashCode()和equals()方法的标准方法。HashCodeBuilder: http://commons.apache.org/lang/api-2.3/org/apache/commons/lang/builder/HashCodeBuilder.htmlEqualsBuilder: http://commons.apache.org/lang//api-2.4/org/apache/commons/lang/builder/EqualsBuilder.html - Gaurav Saini
@subtenante:我的观点是关于hashCode和equals的 - 如果您计划将对象添加到Sets/Maps中,这两个方法都应该被实现。而且,我们不要谈论public int hashCode() { return 1; } ;-) 我相信有很多地方都会批评这种写法。 - Robert Munteanu
显示剩余2条评论

25

将它们放入一个TreeSet中,并使用自定义比较器来检查您需要的属性:

SortedSet<MyObject> set = new TreeSet<MyObject>(new Comparator<MyObject>(){

    public int compare(MyObject o1, MyObject o2) {
         // return 0 if objects are equal in terms of your properties
    }
});

set.addAll(myList); // eliminate duplicates

3
仅在比较器与equals()方法一致的情况下有效,此时最好使用HashSet。 - Michael Myers
3
为什么需要与 equals() 方法保持一致?通常情况下是这样的,但现在我们需要根据自定义条件去除一些重复的元素。使用 Comparator 是我能想到的最不影响原有代码的方法。 - Robert Munteanu
1
有趣的是,TreeSet文档指出如果Comparator与equals()不一致会有问题,而TreeMap的文档(TreeSet基于此)仅表示在这种情况下将忽略equals()。我现在认为这是最好的答案。+1 - Michael Myers
不一致比较器的问题在于当你使用TreeSet并将其作为Set传递时,消费者期望它遵守hashCode()和equals()。然后一切都会失控 :-) - Robert Munteanu

16

11

以上回复的顺序保持版本

return new ArrayList(new LinkedHashSet(recipients));

7
如果您正在使用Eclipse Collections,您可以使用方法distinct()
ListIterable<Integer> integers = Lists.mutable.with(1, 3, 1, 2, 2, 1);
Assert.assertEquals(
    Lists.mutable.with(1, 3, 2),
    integers.distinct());

使用 distinct() 而不是将 List 转换为 Set 再转回 List 的优点在于 distinct() 保留了原始 List 的顺序,保留了每个元素的第一次出现。它通过同时使用 Set 和 List 实现。
MutableSet<T> seenSoFar = Sets.mutable.with();
int size = list.size();
for (int i = 0; i < size; i++)
{
    T item = list.get(i);
    if (seenSoFar.add(item))
    {
        targetCollection.add(item);
    }
}
return targetCollection;

如果您无法将原始列表转换为Eclipse Collections类型,则可以使用ListAdapter来获得相同的API。
MutableList<Integer> distinct = ListAdapter.adapt(integers).distinct();

注意:我是 Eclipse Collections 的贡献者。

4
您可以使用一个 Set。有几种不同的实现:
  • HashSet 使用对象的 hashCodeequals 方法。
  • TreeSet 使用 Comparable 接口定义的 compareTo 方法或者 Comparator 接口定义的 compare 方法。请注意,比较方式必须与 equals 方法一致。更多信息请参考 TreeSet Java 文档。
此外,请记住,如果您重写了 equals 方法,您必须重写 hashCode 方法,以使两个相等的对象具有相同的哈希码。

3

通常的做法是将其转换为Set,然后再转换为List。但你可以使用Functional Java来变得更加高级。如果您喜欢Lamdaj,您会喜欢FJ。

recipients = recipients
             .sort(recipientOrd)
             .group(recipientOrd.equal())
             .map(List.<Recipient>head_());

您需要为收件人定义一个顺序,例如recipientOrd。类似于以下内容:
Ord<Recipient> recipientOrd = ord(new F2<Recipient, Recipient, Ordering>() {
  public Ordering f(Recipient r1, Recipient r2) {
    return stringOrd.compare(r1.getEmailAddress(), r2.getEmailAddress());
  }
});

即使您无法控制收件人类上的equals()hashCode()方法,也可以正常工作。


为什么需要为对象添加排序? - javacavaj
1
你需要排序,这样排序方法才知道如何对它们进行排序。 - Apocalisp

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接