Java - 如何根据自定义比较最好地执行类似集合的操作(例如retainAll)

时间:2011-05-09 22:49:53

标签: java set

我有两个包含相同对象类型的集合。我希望能够访问以下内容:

  • 两组的交集
  • 第1组中包含的对象,而不是第2组
  • 第2组中包含的对象,而不是第1组

我的问题涉及如何最好地比较两组以获得所需的视图。有问题的类有许多id属性,可用于唯一标识该实体。但是,类中还有许多描述对象当前状态的属性。这两个集合可以包含根据id匹配但是处于不同状态的对象(因此,并非所有属性在两个对象之间都相等)。

那么 - 我如何才能最好地实施我的解决方案。要为不考虑状态属性的类实现equals()方法,只查看id属性,对于名称'equals'来说似乎不太正确,以后可能会让人感到困惑。有没有什么方法可以提供一种方法来比较set方法?

此外,我希望能够在不修改原始集的情况下访问上述3个视图。

非常感谢所有帮助!

3 个答案:

答案 0 :(得分:4)

(编辑:我的第一个建议已被删除,因为TreeSet中有一个不幸的实施细节,正如Martin Konecny所指出的那样。一些集合类(例如TreeSet)允许你提供用于比较元素的Comparator,因此您可能希望使用其中一个类 - 至少,如果有一些自然的方式来排序对象。

如果不是(即如果实现CompareTo()会很困难,虽然实现HashCode()Equals()会更简单),你可以创建一个实现这两个的包装类通过查看它们包装的对象中的相关字段来创建函数,并创建这些包装器对象的常规HashSet

答案 1 :(得分:3)

简短版本:根据实体的密钥实施equals,而不是州。

版本稍长:equals方法应检查的内容取决于对象的类型。对于被视为“值”对象的内容(例如,IntegerStringAddress),相等通常基于所有字段相同。对于具有唯一标识它的字段集(其主键)的对象,相等性通常仅基于主键的字段。平等并不一定需要(通常也不应该)考虑对象的状态。它需要确定两个对象是否是同一事物的表示。此外,对于Set中使用的对象或Map中的键,用于确定相等性的字段通常不应该是可变的,因为更改它们可能会导致Set / Map停止按预期工作。

在您实施equals之后,您可以使用Guava查看两个集合之间的差异:

Set<Foo> notInSet2 = Sets.difference(set1, set2);
Set<Foo> notInSet1 = Sets.difference(set2, set1);

两个差异集都将是原始集的实时视图,因此对原始集的更改将自动反映在其中。

答案 2 :(得分:1)

这是标准C ++库与其set type相比更好的要求,candidate key为此目的接受比较器。在Java库中,您的需求通​​过Map更好地建模 - 从Java's TreeSet到其他状态相关字段的映射,或者恰好包含候选对象的完整对象键。 (请注意,C ++ set类型被强制为某种平衡树,通常实现为红黑树,这意味着它等同于{{3}},接受自定义Comparator。)复制数据很难看,但尝试解决这个问题也很难看,正如您已经找到的那样。

如果您可以控制相关类型并将其拆分为单独的候选键和状态部分,则可以消除重复。如果你不能走得那么远,可以考虑将候选键字段组合成一个较大的完整对象中的单个对象;这样,Map密钥类型将与该候选密钥类型相同,唯一的存储开销将是映射密钥的对象引用。候选关键数据不会重复。

请注意,大多数集合类型都是作为封面下的地图实现的;它们从将要设置的元素类型映射到类似布尔标志的类型。显然,有太多的代码会在完全不相交的集合和地图类型中重复。一旦你意识到这一点,从一个尴尬的方式使用一个集合备份到使用一个地图似乎不再强加你认为会存储的开销。

这是一个有点令人沮丧的实现,选择了数学上正确的理想化数据结构,只是发现它是一层或两层的错误选择,但即使在你的情况下你的问题听起来更适合地图表示而不是集合。可以将其视为索引