树集给出不正确的输出 - Java8

2022-09-01 00:36:08

在使用树集时,我发现了非常奇特的行为。

根据我的理解,以下程序应打印两行相同的行:

public class TestSet {
    static void test(String... args) {
        Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
        s.addAll(Arrays.asList("a", "b"));
        s.removeAll(Arrays.asList(args));
        System.out.println(s);
    }

    public static void main(String[] args) {
        test("A");
        test("A", "C");
    }
}

但奇怪的是,它打印了:

[b]
[a, b] 

我无法理解 - 为什么树集会这样?


答案 1

发生这种情况是因为 SortedSet 的比较器用于排序,但 removeAll 依赖于每个元素的方法。从排序集文档中equals

请注意,如果排序集要正确实现接口,则由排序集(无论是否提供显式比较器)维护的排序必须与 equals 一致。(请参阅接口或接口,了解与 equals 一致的精确定义。之所以如此,是因为接口是根据操作定义的,但是排序集使用其(或)方法执行所有元素比较,因此从排序集的角度来看,此方法认为相等的两个元素是相等的。排序集的行为明确定义的,即使它的排序与 equals 不一致;它只是不遵守接口的一般合同。SetComparableComparatorSetequalscompareTocompareSet

“与等价一致”的解释在可比文档中定义:

一个类的自然排序被称为与等数一致,当且仅当具有与每个类相同的布尔值。请注意,它不是任何类的实例,并且应该抛出一个,尽管返回 。Ce1.compareTo(e2) == 0e1.equals(e2)e1e2Cnulle.compareTo(null)NullPointerExceptione.equals(null)false

强烈建议(尽管不是必需的)自然排序与等阶一致。之所以如此,是因为没有显式比较器的排序集(和排序映射)在与自然排序与 equals 不一致的元素(或键)一起使用时,行为“奇怪”。特别是,这样的排序集(或排序映射)违反了集合(或map)的总协定,该总协定是根据方法定义的。equals

总之,集合的比较器的行为与元素的方法不同,从而导致异常(尽管可预测)的行为。equals


答案 2

好吧,这让我感到惊讶,我不知道我是否正确,但是看看这个实现:AbstractSet

public boolean removeAll(Collection<?> c) {
  Objects.requireNonNull(c);
  boolean modified = false;

  if (size() > c.size()) {
    for (Iterator<?> i = c.iterator(); i.hasNext(); )
      modified |= remove(i.next());
  } else {
    for (Iterator<?> i = iterator(); i.hasNext(); ) {
      if (c.contains(i.next())) {
        i.remove();
        modified = true;
      }
    }
  }
  return modified;
}

基本上,在您的示例中,set 的大小等于要删除的参数的大小,因此调用 else 条件。在这种情况下,需要检查参数集合是否删除迭代器的当前元素,并且该检查区分大小写,因此它会检查是否并返回 false,因为包含 ,not ,因此不会删除该元素。请注意,当您将元素添加到集合中时,它可以正常工作,因为现在为 true,因此不再进行检查。containsc.contains("a")c"A""a"s.addAll(Arrays.asList("a", "b", "d"));size() > c.size()contains


推荐