3

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

但是当使用 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;
}

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

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

4

6 回答 6

6

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

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

于 2012-04-25T12:46:44.117 回答
3

尝试使用以下比较器(例如):

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

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

于 2012-04-25T18:26:21.913 回答
2

我不确定这样做是不是个好主意。从 javadoc 为Comparator

当使用能够施加与等于不一致的排序的比较器来对排序集(或排序映射)进行排序时,应谨慎行事。假设带有显式比较器 c 的有序集合(或有序映射)与从集合 S 中提取的元素(或键)一起使用。如果 c 对 S 施加的排序与 equals 不一致,则有序集合(或有序映射)将表现得“奇怪”。特别是有序集合(或有序映射)将违反集合(或映射)的一般合同,它是根据等式定义的。

于 2012-04-25T12:48:34.770 回答
0

您可能想要使用 aSortedSet<Collection<String>>或类似的,因为 - 正如您所提到的 - sorted 不允许您添加多个相等的条目。

或者,您可以使用 Guava 的MultiSet

从 JavaDoc 开始SortedSet

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

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

于 2012-04-25T12:55:11.857 回答
0

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

于 2012-04-25T12:51:43.277 回答
-2

只有当两个引用引用同一个对象时,比较器实际上才应该返回 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;
   } 
}
于 2012-04-25T12:54:37.363 回答