明智地使用重载

时间:2015-07-29 22:42:58

标签: java data-structures collections constructor overloading

  

除了标准的TreeSet之外,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
     

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

TreeSet(Collection<? extends E> c)
     

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

此问题折磨了Framework中所有已排序集合的构造函数(TreeSet,TreeMap,ConcurrentSkipListSet和ConcurrentSkipListMap)。要在您自己的类设计中避免它,请为不同的重载选择参数类型,以便不能将适合于一个重载的类型的参数强制转换为适合不同类型的类型。如果不可能,则应将两个重载设计为使用相同的参数进行相同的操作,而不管其静态类型如何。例如,从集合构造的PriorityQueue使用原始的排序,无论提供构造函数的静态类型是包含Comparator的类型PriorityQueue还是SortedSet之一,或者只是普通的Collection。为了实现这一点,转换构造函数使用所提供集合的Comparator,如果没有,则只返回自然顺序。

目前,我正在阅读一本名为Java Generics and Collections的书,以上是我不理解的内容[page185~186]。

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

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

3 个答案:

答案 0 :(得分:1)

问题是两个构造函数的行为略有不同,因此违反了所谓的“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。

这本身并不是错误的,但对于您图书馆的用户来说,这可能会让您感到惊讶和困惑!

答案 1 :(得分:0)

提供的代码片段编译得很好。确保您已从TreeSet包中导入java.util(单个导入import java.util.*;应该足以编译此代码。)

关于你的问题:

  1. 此示例说明,如果您有两个带有可以在单个类层次结构中的参数的重载方法,有时很难判断将调用哪个方法。在给定示例中,base同时为SortedSetCollection。当您调用TreeSet的构造函数并将base作为参数传递时,调用哪个构造函数取决于编译时类型base。所以,当你传递它时,&#34;它的类型将是TreeSet,并且将选择具有最具体类型的构造函数(一个需要SortedSet的构造函数)。当您明确地将base转换为Set时,它的编译时类型更改,因此更改调用的构造函数将采用Collection

  2. &#34;转换构造函数&#34;在这种情况下,只是构造函数,它接受一个集合的元素并创建另一个集合。 &#34;明智地使用重载&#34;规则可以应用于任何方法,它不必是构造函数。

  3. 例如,查看remove(int|Integer)的{​​{1}}方法,并尝试确定在哪种情况下将调用哪一种方法。

答案 2 :(得分:0)

  

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

他正在说明继承允许编译器做出他认为开发人员应该明确做出的决策这一事实。

我也读过这本书。上半部分非常有助于理解Java令人困惑的泛型。

编译器正在选择

TreeSet(Collection<? extends E> c)

...或

TreeSet(SortedSet<E> s)

只是一个简单的演员。但是我们传入相同的 base 变量。他说这太贴近了。您不应创建哪些参数属于同一类型层次结构的转换构造函数。即 SortedSet Collection

  

其次,我不太了解“转换构造函数”的概念。

Effective Java ,将“对话构造函数”定义为“复制构造函数”。他提到......

  

接口映射的复制构造函数和...更正确地称为转换构造函数 ...

同样来自https://docs.oracle.com/javase/tutorial/collections/interfaces/collection.html

  

此构造函数(称为转换构造函数)初始化新集合以包含指定集合中的所有元素,无论给定集合的子接口或实现类型如何。换句话说,它允许您转换集合的类型。