使用与 equals() 不一致的比较器聚合相似对象
Aggregating similar objects using comparator without consistency with equals()
我有一组实现 equals()
的不可变对象,它们严格比较它们持有的成员变量。如果成员变量相等,则两个对象相同。 hashCodes()
与此一致。
我希望能够根据更宽松的相似性定义来聚合这些对象。我正在考虑为此使用比较器,以便我可以定义一堆临时相似性规则,但 javadoc 指出比较器应该与 equals()
一致。是否可以打破与 equals()
的比较器合同来实现这一点,或者是否有更好的 method/pattern/strategy 根据一些相似性规则聚合对象?
示例可能包括:
- 位置:
equals()
return如果 LatLng 和地名完全相等则为 true,但比较器 returns 0 如果 LatLng 在 25m/50m/100m 以内,而不管地名等, 或者如果无论 LatLng 只有地名相等。
- 日期:
equals()
returns true if long millis are equal, but comparator returns 0 if on the same day/month/year etc..
- 字符串:如果
equalsIgnoreCase()
为真,则 equals()
return 为真,但比较器可能会删除 spaces/special 个字符以简化为某种规范形式,然后 运行 equals
等
考虑到"similarity"不是传递操作:如果a类似于b,并且b类似于c,那么a可能不类似于c,这里建议不要用Comparable
/Comparator
,因为它的契约隐含了传递性。
适合您需要的自定义界面应该是一个不错的选择:
interface SimilarityComparable<T, D> {
// D - object that represents similarity level
D getSimilarityLevel(T other);
}
但是,由于 Java 泛型:
,使用这种方法您将无法为同一类型定义多组相似性
class Location implements SimilarityComparable<Location, Distance>, SimilarityComparable<Location, NameDifference> {
// won't compile - can't use two generic interfaces of the same type simultaneously
}
在这种情况下,您可以回退到比较器:
interface SimilarityComparator<T, D> {
D getSimilarityLevel(T a, T b);
}
class LocationDistanceSimilarityComparator implements SimilarityComparator<Location, Distance> {
...
}
class LocationNameSimilarityComparator implements SimilarityComparator<Location, NameDifference> {
...
}
答案将取决于您打算如何使用比较。
但是,我同意@user3707125,在任何情况下都不要为此目的实施Comparable
。您无法控制哪些代码将使用 Comparable
函数进行必须遵循定义的规则的比较。
在某些情况下您 必须 使用 Comparator
,例如如果您使用标准 Java API 库来排序或过滤你的对象。如果是这种情况,那么您仍然可以安全地使用 Comparator
,因为您可以控制在何处使用 Comparator
,以及在何处使用更严格的 equals()
。您只需要注意,如果您的宽松比较不符合标准合同,那么排序和筛选操作将产生同样宽松且不一致的结果。
如果您不必使用 Comparator
那么就不要使用。您可以自由定义自己的方法和接口,以您喜欢的任何方式做任何您想做的事。打败自己!
我有一组实现 equals()
的不可变对象,它们严格比较它们持有的成员变量。如果成员变量相等,则两个对象相同。 hashCodes()
与此一致。
我希望能够根据更宽松的相似性定义来聚合这些对象。我正在考虑为此使用比较器,以便我可以定义一堆临时相似性规则,但 javadoc 指出比较器应该与 equals()
一致。是否可以打破与 equals()
的比较器合同来实现这一点,或者是否有更好的 method/pattern/strategy 根据一些相似性规则聚合对象?
示例可能包括:
- 位置:
equals()
return如果 LatLng 和地名完全相等则为 true,但比较器 returns 0 如果 LatLng 在 25m/50m/100m 以内,而不管地名等, 或者如果无论 LatLng 只有地名相等。 - 日期:
equals()
returns true if long millis are equal, but comparator returns 0 if on the same day/month/year etc.. - 字符串:如果
equalsIgnoreCase()
为真,则equals()
return 为真,但比较器可能会删除 spaces/special 个字符以简化为某种规范形式,然后 运行equals
等
考虑到"similarity"不是传递操作:如果a类似于b,并且b类似于c,那么a可能不类似于c,这里建议不要用Comparable
/Comparator
,因为它的契约隐含了传递性。
适合您需要的自定义界面应该是一个不错的选择:
interface SimilarityComparable<T, D> {
// D - object that represents similarity level
D getSimilarityLevel(T other);
}
但是,由于 Java 泛型:
,使用这种方法您将无法为同一类型定义多组相似性class Location implements SimilarityComparable<Location, Distance>, SimilarityComparable<Location, NameDifference> {
// won't compile - can't use two generic interfaces of the same type simultaneously
}
在这种情况下,您可以回退到比较器:
interface SimilarityComparator<T, D> {
D getSimilarityLevel(T a, T b);
}
class LocationDistanceSimilarityComparator implements SimilarityComparator<Location, Distance> {
...
}
class LocationNameSimilarityComparator implements SimilarityComparator<Location, NameDifference> {
...
}
答案将取决于您打算如何使用比较。
但是,我同意@user3707125,在任何情况下都不要为此目的实施Comparable
。您无法控制哪些代码将使用 Comparable
函数进行必须遵循定义的规则的比较。
在某些情况下您 必须 使用 Comparator
,例如如果您使用标准 Java API 库来排序或过滤你的对象。如果是这种情况,那么您仍然可以安全地使用 Comparator
,因为您可以控制在何处使用 Comparator
,以及在何处使用更严格的 equals()
。您只需要注意,如果您的宽松比较不符合标准合同,那么排序和筛选操作将产生同样宽松且不一致的结果。
如果您不必使用 Comparator
那么就不要使用。您可以自由定义自己的方法和接口,以您喜欢的任何方式做任何您想做的事。打败自己!