明智地使用重载

CSn*_*erd 8 java collections constructor overloading data-structures

TreeSet 的构造函数除了标准构造函数之外,还包括一个允许您提供 Comparator 的构造函数和一个允许您从另一个 SortedSet 创建一个的构造函数:

TreeSet(Comparator<? super E> c)
// construct an empty set which will be sorted using the
// specified comparator 
TreeSet(SortedSet<E> s)
// construct a new set containing the elements of the 
// supplied set, sorted according to the same ordering
Run Code Online (Sandbox Code Playgroud)

其中的第二个在其声明中与标准的“转换构造函数”太接近了:

TreeSet(Collection<? extends E> c)
Run Code Online (Sandbox Code Playgroud)

正如 Joshua Bloch 在 Effective Java(方法一章中的“明智地使用重载”项)中所解释的那样,调用带有相关类型参数的两个构造函数或方法重载之一可能会产生令人困惑的结果。这是因为,在 Java 中,对重载构造函数和方法的调用是在编译时根据参数的静态类型解析的,因此对参数应用强制转换会对调用结果产生很大的影响,因为以下代码显示:

// construct and populate a NavigableSet whose iterator returns its
// elements in the reverse of natural order:
NavigableSet<String> base = new TreeSet<String>(Collections.reverseOrder());
Collections.addAll(base, "b", "a", "c");

// call the two different constructors for TreeSet, supplying the
// set just constructed, but with different static types: 
NavigableSet<String> sortedSet1 = new TreeSet<String>((Set<String>)base);
NavigableSet<String> sortedSet2 = new TreeSet<String>(base);
// and the two sets have different iteration orders: 
List<String> forward = new ArrayList<String>(); 
forward.addAll(sortedSet1);
List<String> backward = new ArrayList<String>(); 
backward.addAll(sortedSet2);
assert !forward.equals(backward); 
Collections.reverse(forward); 
assert forward.equals(backward);
Run Code Online (Sandbox Code Playgroud)

这个问题会影响框架中所有排序集合的构造函数(TreeSet、TreeMap、ConcurrentSkipListSet 和 ConcurrentSkipListMap)。为了在您自己的类设计中避免这种情况,请为不同的重载选择参数类型,这样适合于一个重载的类型的参数就不能转换为适合另一个重载的类型。如果这是不可能的,则应该将两个重载设计为使用相同的参数具有相同的行为,而不管其静态类型如何。例如,从集合构造的 PriorityQueue 使用原始的排序,无论提供构造函数的静态类型是包含 Comparator 的类型 PriorityQueue 或 SortedSet 之一,还是只是普通集合。为此,转换构造函数使用提供的集合的 Comparator,

目前,我正在阅读名为Java泛型和集合的书,以上是我不明白的[page185~186]。

首先,我不太明白它为什么使用这个例子以及它想说明什么。

其次,我不太了解“转换构造函数”的概念。是不是因为转换构造函数的存在,才应该慎重使用重载?

ysh*_*vit 4

问题在于,两个构造函数的行为略有不同,从而违反了所谓的“最小惊讶原则”。

TreeSet(SortedSet<E>)“使用与指定排序集相同的顺序”构造一个新集合,而TreeSet(Collection<? extends E>)使用“其元素的自然顺序”。这意味着使用相同底层实例构建的两个 TreeSet 的行为可能会有所不同,具体取决于构建它们所使用的引用的静态类型。

SortedSet<Integer> original = getReverseSet(); // { 5, 4, 3, 2, 1}
Collection<Integer> alsoOriginal = original; // same instance exactly

TreeSet<Integer> a = new TreeSet<>(original);
TreeSet<Integer> b = new TreeSet<>(alsoOriginal);
Run Code Online (Sandbox Code Playgroud)

乍一看,ab应该是相同的——毕竟,它们是使用相同的实例构建的!但第一个使用TreeSet(SortedSet)构造函数(因此保留反向排序),而第二个使用TreeSet(Collection)构造函数(因此使用元素的自然顺序,这与反向排序不同)。此外,a.comparator()将返回反向比较器,而b.comparator()将返回null。

这本身并没有,但它可能会让您的库的用户感到惊讶和困惑!