【问题标题】:How to make a distinction between two equals objects in a Sorted collection?如何区分 Sorted 集合中的两个 equals 对象?
【发布时间】:2012-05-06 03:34:03
【问题描述】:

我可能错了,但对我来说,我们可以为一个对象覆盖等于,这样你就认为它们有意义地等于。 映射中的所有条目都有不同的键,集合中的所有条目都有不同的值(没有意义地等于)

但是当使用 TreeMap 或 TreeSet 时,您可以提供一个比较器。 我注意到,当提供比较器时,对象的 equals 方法被绕过,当比较器返回 0 时,两个对象被认为是相等的。 因此,我们有 2 个对象,但在映射键集或集合中,只保留了一个。

我想知道是否有可能使用排序集合来区分两个不同的实例。

这是一个简单的示例:

public static void main(String[] args) {
    TreeSet<String> set = new TreeSet<String>();
    String s1 = new String("toto");
    String s2 = new String("toto");
    System.out.println(s1 == s2);
    set.add(s1);
    set.add(s2);
    System.out.println(set.size());
}

请注意,使用 new String("xxx") 会绕过字符串池的使用,因此 s1 != s2。 我想知道如何实现一个比较器,以便设置大小为 2 而不是 1。

主要问题是:对于相同字符串值的两个不同实例,我如何在比较器中返回 != 0 ?

请注意,我希望该比较器遵守规则:

比较它的两个参数的顺序。返回负整数、零或正整数,因为第一个参数小于、等于或大于第二个。实现者必须确保所有 x 和 y 的 sgn(compare(x, y)) == -sgn(compare(y, x))。 (这意味着当且仅当 compare(y, x) 抛出异常时 compare(x, y) 必须抛出异常。)

实现者还必须确保关系是可传递的:((compare(x, y)>0) && (compare(y, z)>0)) 意味着 compare(x, z)>0。

最后,实现者必须确保 compare(x, y)==0 意味着所有 z 的 sgn(compare(x, z))==sgn(compare(y, z))。

一般是这样,但不严格要求 (compare(x, y)==0) == (x.equals(y))。一般来说,任何违反此条件的比较器都应清楚地表明这一事实。推荐的语言是“注意:这个比较器强加了与等号不一致的顺序。”

我可以使用这样的技巧:

public int compare(String s1,String s2) {
  if s1.equals(s2) { return -1 }
  ...
}

它似乎工作正常,但由于 compare(s1,s2) != -compare(s2,s1) 没有遵守规则

那么这个问题有什么优雅的解决方案吗?


编辑:对于那些想知道我为什么要问这样的事情的人。这更多是出于好奇,而不是任何现实生活中的问题。

但是我已经遇到过这样的情况,虽然关于这个问题的解决方案:

想象一下你有:

class Label {
  String label;
}

对于每个标签,您都有一个关联的字符串值。 现在,如果你想要一个地图,标签-> 值怎么办。 但是现在,如果您希望能够拥有与地图键相同的两倍标签怎么办? 前任 “标签”(ref1)-> value1 “标签”(ref2)-> value2 您可以实现等于,以便两个不同的 Label 实例不等于 -> 我认为它适用于 HashMap。

但是,如果您希望能够按字母顺序对这些 Label 对象进行排序呢? 您需要提供比较器或实现可比较。 但是我们如何才能区分具有相同标签的 2 个标签呢? 我们必须! compare(ref1,ref2) 不能返回 0。但它应该返回 -1 还是 1 ? 我们可以比较内存地址或类似的东西来做出这样的决定,但我认为这在 Java 中是不可能的......

【问题讨论】:

  • 我的意见是,在您的情况下,您不应该使用字符串作为标识符(地图的键),因为它们不是真正的标识符。如果你有一个字符串"label",你怎么知道你想要两个Label对象中的哪一个?
  • 这只是一个例子,但实际上我不仅有“标签”,而且我必须分开引用

标签: java guava apache-commons


【解决方案1】:

如果您使用 Guava,则可以使用 Ordering.arbitrary(),这将对在 VM 的生命周期内保持一致的元素施加额外的顺序。您可以使用它以一致的方式打破 Comparator 中的关系。

但是,您可能使用了错误的数据结构。您是否考虑过使用允许添加多个实例的Multiset(例如TreeMultiset)?

【讨论】:

  • 感谢这个有趣的接缝,你知道它是如何在内部真正工作的,以便能够在两个等于但 != 字符串之间产生一个顺序(-1 或 +1)吗?
  • 这很有趣。它使用 System.identityHashCode() 作为第一遍,然后如果有冲突,它会为每个看到的新实例维护一个 UID 列表。见guava-libraries.googlecode.com/svn-history/r311/trunk/javadoc/…
  • 非常有趣。我已经使用哈希码进行区分(返回 hash1 - hash2)但由于(不可能的)冲突而不满意。
  • hashCode 本身是不够的,因为 hashCode() 通常与 equals() 一致。你需要回到对象上的那个;因此 identityHashCode().
  • @Sebastien Lorber:使用return hash1 - hash2 是一个常见错误。由于溢出,结果关系不具有传递性。
【解决方案2】:

我不确定这样做是否是个好主意。来自Comparator的javadoc:

使用比较器时应谨慎 强加一个与等于不一致的排序来排序一个排序集 (或排序的地图)。假设一个有序集合(或有序映射)具有显式 比较器 c 与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与 equals 不一致,排序后的 set(或排序的地图)将表现得“奇怪”。特别是排序的 set(或 sorted map)将违反 set(或 map),它是用equals定义的。

【讨论】:

  • 我理解您的意思,并且看到了一些可能发生的危险行为(例如,使用 key1 进行映射,使用 key2 检索 key1 条目,但最终拥有 !key1.equals(k2))。在我的例子中,这可能是一个问题,但我可以有一个 equals 方法来区分两个不同的实例。
【解决方案3】:

如果您想要一个具有相同对象的排序集合,您可以将所有对象放在一个 List 中并使用 Collections.sort()。

【讨论】:

  • @SebastienLorber - 如果地图包含多个相等的键,您将如何从地图中检索某些内容?
  • 由于实例不同,基本上键不会相等(我的字符串示例可能不合适,因为我们无法编辑该字符串等于)
【解决方案4】:

只有当两个引用指向同一个对象时,比较器才应该返回 0,如下所示:

public int compare(String s1,String s2) {
   if (s1!=s2) { 
      int result = s1.compareTo(s2);
      if (result == 0) {
          return -1;
      } else {
          return result;
      }
   } else {
      return 0;
   } 
}

【讨论】:

  • 我们没有 compare(s1,s2) = -compare(s2,s1) 。
  • 仍然,没有比较(s1,s2) = -compare(s2,s1)
  • 只有当s1 实际上是与s2 相同的字符串时才会发生这种情况。你也需要那个吗?
  • 是的!基本上我的问题是为这个特定案例提供一个代码:) 但已经提供了答案
【解决方案5】:

您可能想要使用 SortedSet&lt;Collection&lt;String&gt;&gt; 或类似名称,因为 - 正如您所提到的 - 排序不允许您添加多个相等的条目。

您也可以使用 Guava 的 MultiSet

来自SortedSet 上的 JavaDoc:

请注意,如果有序集合要正确实现 Set 接口,则由有序集合维护的排序(无论是否提供显式比较器)必须与等于一致

但是,仍然存在一个问题:为什么要拥有两个逻辑上相等的不同实例(这就是 equals() 的实际含义)。

【讨论】:

    【解决方案6】:

    尝试使用以下比较器(以您的示例为例):

    Comparator<String> comp = Ordering.natural().compound(Ordering.arbitrary());
    

    这将根据事物的自然 Comparable 排序对事物进行排序,但当自然排序相等时,它将退回到任意排序,以便不同的对象保持不同。

    【讨论】:

    • 谢谢,我需要那个番石榴任意方法。但是 +1 是因为您明显使用了这样的功能
    猜你喜欢
    • 1970-01-01
    • 2013-10-21
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多