在使用树集时,我发现了非常奇特的行为.根据我的理解,这个程序应该打印两个相同的行:
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");
}
}
Run Code Online (Sandbox Code Playgroud)
但奇怪的是它打印:
[b]
[a, b]
Run Code Online (Sandbox Code Playgroud)
为什么树集会表现得像这样?
VGR*_*VGR 40
这是因为SortedSet的Comparator用于排序,但removeAll依赖于equals
每个元素的方法.从SortedSet文档:
请注意,如果有序集合要正确实现接口,则由有序集合维护的排序(无论是否提供显式比较器)必须与equals一致
Set
.(请参阅Comparable
接口或Comparator
接口以获得与equals一致的精确定义.)这是因为Set
接口是根据equals
操作定义的,但是有序集使用其compareTo
(或compare
)方法执行所有元素比较,因此两个元素是从排序集的角度来看,这种方法被认为是相等的.即使排序与equals不一致,排序集的行为也是明确定义的; 它只是不遵守Set
界面的一般合同.
可比文档中定义了"与equals一致"的解释:
一类的自然顺序
C
被说成是与equals一致当且仅当e1.compareTo(e2) == 0
具有相同的布尔值的e1.equals(e2)
每一个e1
和e2
阶级的C
.请注意,这null
不是任何类的实例,并且e.compareTo(null)
应该抛出一个NullPointerException
偶数e.equals(null)
返回false
.强烈建议(尽管不要求)自然排序与equals一致.这是因为没有显式比较器的有序集(和有序映射)在与自然顺序与equals不一致的元素(或键)一起使用时表现得"奇怪".特别地,这样的有序集(或有序映射)违反了集合(或映射)的一般契约,其根据该
equals
方法定义.
总之,Set的比较器的行为与元素的equals
方法不同,导致异常(虽然可预测)行为.
Sha*_*dov 15
嗯,这让我感到惊讶,我不知道我是否正确,但看看这个实现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;
}
Run Code Online (Sandbox Code Playgroud)
基本上在您的示例中,set的大小等于要删除的参数的大小,因此调用else条件.在那种情况下,检查你的参数集合是否删除contains
了迭代器的当前元素,并且该检查区分大小写,因此它检查是否c.contains("a")
返回false,因为c
包含"A"
,而不是"a"
,因此元素不会被删除.请注意,当您向集合添加元素时,s.addAll(Arrays.asList("a", "b", "d"));
它可以正常工作,因为size() > c.size()
现在是正确的,因此不再进行contains
检查.