java - 明智地使用重载

标签 java data-structures collections constructor overloading

The constructors for TreeSet include, besides the standard ones, one which allows you to supply a Comparator and one which allows you to create one from another 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

The second of these is rather too close in its declaration to the standard "conversion constructor” :

TreeSet(Collection<? extends E> c)

As Joshua Bloch explains in Effective Java (item “Use overloading judiciously” in the chapter on Methods), calling one of two constructor or method overloads which take parameters of related type can give confusing results. This is because, in Java, calls to overloaded constructors and methods are resolved at compile time on the basis of the static type of the argument, so applying a cast to an argument can make a big difference to the result of the call, as the following code shows:

// 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);

This problem afflicts the constructors for all the sorted collections in the Framework (TreeSet, TreeMap, ConcurrentSkipListSet, and ConcurrentSkipListMap). To avoid it in your own class designs, choose parameter types for different overloads so that an argument of a type appropriate to one overload cannot be cast to the type appropriate to a different one. If that is not possible, the two overloads should be designed to behave identically with the same argument, regardless of its static type. For example, a PriorityQueue constructed from a collection uses the ordering of the original, whether the static type with which the constructor is supplied is one of the Comparator-containing types PriorityQueue or SortedSet, or just a plain Collection. To achieve this, the conversion constructor uses the Comparator of the supplied collection, only falling back on natural ordering if it does not have one.

最近在看Java Generics and Collections这本书,上面是我看不懂的地方[page185~186]。

首先,我不太明白为什么要用这个例子,想说明什么。

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

最佳答案

问题在于两个构造函数的行为略有不同,因此违反了所谓的“principle of least astonishment”。

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);

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

这本身错误,但它可能会让图书馆的用户感到惊讶和困惑!

关于java - 明智地使用重载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31712397/

相关文章:

algorithm - AVL树平衡

c# - 如何在 ASP.Net 中使用子控件集合创建控件

java - 常见迭代器错误的解释

c# - 将 NHibernate 集合延迟加载到 ViewModel 中?

java - 使用线程池退出线程的无限循环

java - 带有一对多参数的 Spring 查询

java - 具有多个可变查找属性的集合的正确数据结构

algorithm - 仅使用一个堆栈实现优先级队列

java - 解析和映射具有动态属性的 JSON 对象

java - 当作为查询参数给出更多字符时,REST API 没有响应