AbstractSet.removeAll()方法无法正常工作

时间:2017-03-30 15:09:47

标签: java abstractset

下面显示的代码输出:

并[b]

[a,b]

但是我希望它能在输出中打印两条相同的行。

import java.util.*;

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

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

规范明确指出removeAll

  

“删除包含在其中的所有此集合的元素   指定的集合。“

所以根据我的理解,目前的行为是不可预测的。请帮我理解这个

3 个答案:

答案 0 :(得分:6)

您只能阅读部分文档。您忘记了TreeSet中的一个重要段落:

  

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

现在removeAll实施来自AbstractSet并使用equals方法。根据您的代码,您将"a".equals("A")不是true,因此即使您在TreeSet本身使用了比较器来管理它们,也不会认为这些元素是相等的。如果你尝试使用包装器,那么问题就会消失:

import java.util.*;
import java.lang.*;

class Test
{
    static class StringWrapper implements Comparable<StringWrapper>
    {
      public final String string;

      public StringWrapper(String string)
      {
        this.string = string;
      }

      @Override public boolean equals(Object o)
      { 
        return o instanceof StringWrapper && 
            ((StringWrapper)o).string.compareToIgnoreCase(string) == 0; 
      }

      @Override public int compareTo(StringWrapper other) { 
        return string.compareToIgnoreCase(other.string); 
      }

      @Override public String toString() { return string; }
    }

    static void test(StringWrapper... abc) 
    {
        Set<StringWrapper> s = new TreeSet<>();
        s.addAll(Arrays.asList(new StringWrapper("a"), new StringWrapper("b")));
        s.removeAll(Arrays.asList(abc));
        System.out.println(s);
    }

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

这是因为您现在在对象的equalscompareTo之间提供了一致的实现,因此在排序集内如何添加对象以及如何在所有抽象行为之间不会出现不一致的行为。集合使用它们。

一般情况下,对于Java代码,这是一种三条规则:如果您实现compareToequalshashCode,则应始终实施所有这些规则以避免出现问题标准集合(即使hashCode不太重要,除非您在任何散列集合中使用这些对象)。这在java文档中多次指定。

答案 1 :(得分:3)

这与TreeSet<E>的实现不一致,接近bug。当您传递给removeAll的集合中的项目数大于或等于集合中的项目数时,代码将忽略自定义比较器。

不一致是由小优化造成的:如果您查看removeAll的{​​{3}},AbstractSet继承自public boolean removeAll(Collection<?> 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; } ,则优化如下:

c

equals的项目数少于此项目(顶部分支)时,您可以看到行为不同,而当它有多个或多个项目(底部分支)时,行为会有所不同。

Top branch使用与此set相关联的比较器,而bottom branch使用c.contains(i.next())进行比较s.addAll(Arrays.asList("x", "z", "a", "b")); - 所有这些都采用相同的方法!

您可以通过向原始树集添加一些额外元素来演示此行为:

remove(i.next())

现在两个测试用例的输出变得相同,因为function SortUpdate() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var input = ss.getSheetByName("Input"); var inputData = input.getDataRange();//This returns a range not data var lastRow = inputData.getNumRows(); inputData = input.getRange(2, 1, lastRow, 17);//The inputData variable has received two assignments which one do you want? Both are ranges and do not have any data. for(var i = 9; i<=(15);i++){ inputData.sort({column: i, ascending: false}); } } 使用了集合的比较器。

答案 2 :(得分:1)

原因是你使用的比较器String.CASE_INSENSITIVE_ORDER与equals不一致。

TreeSet所述:

  

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

Comparable

所述的平等一致
  

C类的自然顺序被认为与equals一致,当且仅当   e1.compareTo(e2)== 0与e1.equals(e2)具有相同的布尔值   对于C类的每个e1和e2。

作为不区分大小写的比较器的示例,您可以使用:

"a".compareTo("A") == 0 => true

,而

"a".equals("A") => false