【问题标题】:compareTo involving non-comparable field: how to maintain transitivity?compareTo 涉及非可比领域:如何保持传递性?
【发布时间】:2012-09-20 14:40:35
【问题描述】:

考虑一个具有可比较(与 equals 一致)和不可比较字段(我不知道它是否覆盖 Object#equals 的类)的类。

应比较类的实例,其中结果顺序应与 equals 一致,即如果两个字段相等(根据 Object#equals)并与可比较字段的顺序一致,则返回 0。我使用System.identityHashCode 涵盖了这些要求未涵盖的大多数情况(具有相同可比性但其他值不同的实例的顺序是任意的),但不确定这是否是最佳方法。

public class MyClass implements Comparable<MyClass> {
    private Integer intField;
    private Object nonCompField;

    public int compareTo(MyClass other) {
        int intFieldComp = this.intField.compareTo(other.intField);
        if (intFieldComp != 0)
            return intFieldComp;
        if (this.nonCompField.equals(other.nonCompField))
            return 0;
        // ...and now? My current approach:
        if (Systems.identityHashCode(this.nonCompField) < Systems.identityHashCode(other.nonCompField))
            return -1;
        else
            return 1;
     }
}

我在这里看到两个问题:

  • 如果两个对象的Systems.identityHashCode 相同,则每个对象都大于另一个对象。 (这会发生吗?)
  • 据我了解Systems.identityHashCode 的作用,具有相同intField 值和不同nonCompField 值的实例的顺序在程序运行之间不必保持一致。

正确吗?还有更多问题吗?最重要的是,有没有办法解决这个问题?

【问题讨论】:

  • 为什么要比较这样的对象,又为什么要比较的对象包括明确不可比较的字段?
  • @LouisWasserman 在依赖于内部排序的数据结构中使用它,例如TreeMap
  • 这并没有真正解决问题。你为什么想要这样的事情,例如在TreeMap?你能解释一下你实际上在现实世界中试图做什么来激发这个问题吗?
  • @LouisWasserman 我在一个更大的代码库中遇到了一个类,它有一个比较数字字段的比较器,如果这些字段相等且不可比较字段不相等,则返回 1。我试图修复它,但意识到即使我的上述方法也不起作用,因为该类用于类似于TreeSet 的排序数据结构,但实际上依赖于违反比较器的对称性。我记得以前在其他地方使用过上述方法,但现在我想我也可以一起使用(排序的)ListHashMap,而不是那里的一个 TreeMap。

标签: java compareto


【解决方案1】:

第一个问题虽然不太可能发生,但可能会发生(我认为您需要大量内存,而且运气非常糟糕)。但是 Guava 的Ordering.arbitrary() 解决了这个问题,它在后台使用身份哈希码,但是对于两个不同对象具有相同身份哈希码的情况,维护了一个比较结果的缓存。

关于您的第二个问题,不,在运行之间不会保留身份哈希码。

【讨论】:

    【解决方案2】:

    Systems.identityHashCode [...] 两个对象相同 [...] (这会发生吗?)

    是的,它可以。引用自Java API Documentation

    在合理可行的情况下,Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。
    identityHashCode(Object x) 为给定的对象返回与默认方法hashCode(),无论给定对象的类是否覆盖hashCode()

    因此您可能会遇到哈希冲突,并且随着内存不断增长但哈希码保持固定在 32 位,它们将变得越来越有可能。

    据我了解Systems.identityHashCode 的作用,具有相同intField 值和不同nonCompField 值的实例的顺序在程序运行之间不必保持一致。

    没错。它甚至可能在同一个程序的一次调用中有所不同:即使foo.equals(baz),您也可以拥有(1,foo) &lt; (1,bar) &lt; (1,baz)

    最重要的是,有没有办法解决这个问题?

    您可以维护一个映射,该映射将不可比较类型的每个不同值映射到一个序列号,您遇到的每个不同值都会增加该序列号。

    不过,内存管理会很棘手:您不能使用WeakHashMap,因为代码可能会使您的关键对象无法访问,但仍保留对另一个具有相同值的对象的引用。因此,您要么维护一个对给定值的所有对象的弱引用列表,要么简单地使用强引用并接受这样一个事实,即遇到的任何不可比较的值都不会被垃圾回收。

    请注意,除非您以相同的顺序可重复地创建值,否则此方案仍不会产生可重复的序列号。

    【讨论】:

    • 我现在意识到我在写问题标题时实际上是在考虑对称性而不是传递性。关于“(1,foo) &lt; (1,bar) &lt; (1,baz) 尽管foo.equals(baz)”的观点非常好。
    【解决方案3】:

    如果 nonCompField 的类已经实现了一个相当好的 toString(),你也许可以使用

    return String.valueOf(this.nonCompField).compareTo(String.valueOf(other.nonCompField));
    

    不幸的是,默认的 Object.toString() 使用哈希码,这有其他人指出的潜在问题。

    【讨论】:

      猜你喜欢
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多