在使用树集时,我发现了非常奇怪的行为。
根据我的理解,以下程序应打印两条相同的行:
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]
我无法理解-为什么树集的行为如此?
发生这种情况是因为SortedSet的Comparator用于排序,但是removeAll依赖于equals每个元素的方法。从SortedSet文档中:
equals
请注意,如果排序集要正确实现接口,则排序集(无论是否提供显式比较器)所维护的顺序必须 与equals一致Set。(请参见Comparable接口或Comparator接口,以获得 与equals一致 的精确定义 。 )之所以如此,是因为Set接口是根据equals操作定义的,但是排序后的集合使用其compareTo(或compare)方法执行所有元素比较,因此两个元素从排序集的角度来看,此方法认为相等是相等的。即使排序顺序与equals不一致,也 可以 很好地定义排序集的行为。它只是不遵守总合同Set 接口。
Set
Comparable
Comparator
compareTo
compare
可比文档中定义了“等于”的解释:
一类的自然顺序C被说成是 与equals一致 当且仅当e1.compareTo(e2) == 0具有相同的布尔值的e1.equals(e2)每一个e1和e2阶级的C。请注意,null不是任何类的实例,并e.compareTo(null)应抛出NullPointerException,即使e.equals(null)回报false。 强烈建议(尽管不是必需的)自然顺序应与等号保持一致。之所以如此,是因为没有显式比较器的排序集(和排序映射)在与自然排序与等式不一致的元素(或键)一起使用时,表现为“奇怪”。特别是,这样的排序集(或排序图)违反了根据equals方法定义的集合(或图)的一般约定。
一类的自然顺序C被说成是 与equals一致 当且仅当e1.compareTo(e2) == 0具有相同的布尔值的e1.equals(e2)每一个e1和e2阶级的C。请注意,null不是任何类的实例,并e.compareTo(null)应抛出NullPointerException,即使e.equals(null)回报false。
C
e1.compareTo(e2) == 0
e1.equals(e2)
e1
e2
null
e.compareTo(null)
NullPointerException
e.equals(null)
false
强烈建议(尽管不是必需的)自然顺序应与等号保持一致。之所以如此,是因为没有显式比较器的排序集(和排序映射)在与自然排序与等式不一致的元素(或键)一起使用时,表现为“奇怪”。特别是,这样的排序集(或排序图)违反了根据equals方法定义的集合(或图)的一般约定。
总之,集合的比较器的行为与元素的equals方法不同,从而导致异常(尽管可预测)行为。