为什么方法签名有这种差异?

时间:2013-07-04 14:59:23

标签: java api generics

从此API

1)以下是TreeMap的构造函数,它使用另一个映射并使用类似的接口(传递的映射)对其进行排序。

TreeMap

public TreeMap(Map<? extends K,? extends V> m)

Constructs a new tree map containing the same mappings as the given map, ordered according to the natural ordering of its keys. All
     插入新地图的

键必须实现Comparable   接口。此外,所有这些密钥必须相互比较:   k1.compareTo(k2)不得为任何键k1抛出ClassCastException   和地图中的k2。此方法在n * log(n)时间内运行。

Parameters:
    m - the map whose mappings are to be placed in this map
Throws:
    ClassCastException - if the keys in m are not Comparable, or are not mutually comparable
    NullPointerException - if the specified map is null

2)以下是TreeMap的构造函数,它接受另一个映射并使用比较器接口(传递的映射)对其进行排序。

  

TreeMap的

public TreeMap(SortedMap<K,? extends V> m)

Constructs a new tree map containing the same mappings and using the same ordering as the specified sorted map. This method runs in
     

线性时间。

Parameters:
    m - the sorted map whose mappings are to be placed in this map, and whose comparator is to be used to sort this map
Throws:
    NullPointerException - if the specified map is null

为什么第一个签名是public TreeMap(Map<? extends K, ? extends V> m),第二个签名是public TreeMap(SortedMap<K,? extends V> m)

更新:如果问题不够清楚,我想知道为什么与构造函数中的KEYS参数相关的泛型部分彼此不同。 ? extends KK

2 个答案:

答案 0 :(得分:2)

Map构造函数使用? extends K,因为尽可能使它尽可能宽松总是好的。

SortedMap构造函数使用K,因为它接受另一个地图的比较器,如果边界是? extends K,则不会进行类型检查。

让我更详细地解释一下。

假设我们有课程BaseMiddle extends BaseDerived extends Middle implements Foo

然后我们有两个特殊的比较器,BaseComp implements Comparator<Base>FooComp implements Comparator<Foo>

现在我们创建一个TreeMap<Derived, String>并给它一个FooComp

SortedMap<Derived, String> sm1 = new TreeMap<Derived, String>(new FooComp());

请注意我们使用的签名为TreeMap(Comparator<? super K> comp)的构造函数。 FooComp实现了Comparator<Foo>,并且由于FooDerived(我们的K)的超类,所以这个类型检查。

接下来,我们来看看这张地图:

SortedMap<Derived, String> sm2 = new TreeMap<Derived, String>(new BaseComp());

再次这很好。 BaseComp实施Comparator<Base>Base super Derived类型检查。

好的,让我们进一步假设采用有序地图的构造函数具有与普通地图相同的签名TreeMap(SortedMap<? extends K, ? extends V> map)。然后我们现在可以这样说:

SortedMap<Middle, String> sm3 = new TreeMap<Middle, String>(sm2);

然后构造函数将使用sm2的比较器,即BaseComp,这很好:Base super Middle仍然是typechecks。但是,我们也可以这样做:

SortedMap<Middle, String> sm4 = new TreeMap<Middle, String>(sm1);

构造函数使用sm1的比较器,FooComp,而Foo super Middle不正确。因此,如果允许这样做,那就不安全了。

然而,这是不允许的。特别是,假设您更改了构造函数的边界。我要为问号分配数字 - 不是有效的语法,但有助于理解。首先,你有SortedMap接口,可以访问比较器:

public interface SortedMap<K1, V1> extends Map<K1, V1> {
    Comparator<?1 super K1> comparator();
}

然后你有TreeMap类及其构造函数:

public class TreeMap<K2, V2> ... {
    private Comparator<?2 super K2> comp;

    public TreeMap(SortedMap<?3 extends K2, ?4 extends V2> map) {
        this.comp = map.comparator();
        // magic stuff to transfer elements
    }
}

好的,在我们的具体案例中,我们尝试new TreeMap<Middle, String>((SortedMap<Derived, String>)x),因此请匹配以下类型:K1 = DerivedK2 = Middle。我会忽略这些价值观,它们并不重要。

构造函数的签名很好。 ?3推断为K1DerivedDerived extends Middle类型检查。

构造函数的第一行是有趣的部分。 this.comp的类型为Comparator<?2 super Middle>。根据{{​​1}}定义的map.comparator()类型为SortedMap,因此在我们的实例中它是?1 super K1

因此,我们尝试将?1 super Derived分配给?1 super Derived。为了使其正常工作,?2 super Middle的每个基本类型也必须是Derived的基本类型。但我们已经展示了一个反例:MiddleFoo但不是Derived实现的界面是一个。如果MiddleMiddle2之间有Middle,那就是另一个。

因此编译器不能允许这个赋值,因此必须无法编译代码。

这很有趣!

答案 1 :(得分:1)

两者都是有效的构造函数,但作为约定,应始终使用的最高接口。因此,虽然采用Map的构造函数在所有情况下都可以工作,但是当您从SortedMap构建TreeMap时,应该使用SortedMap构造函数。

但为什么在这种情况下呢?因为通过让构造函数知道您没有传递常规Map,而是已经排序的地图,它可能会利用它并以最佳方式运行。在这种特殊情况下,很容易理解为什么从已经排序的地图构建TreeMap(它是一个有序地图)比从非有序地图(如Hasmap)构建它更快:构造函数将具有首先订购地图,但它会在SortedMap 上跳过此部分,因为它已经订购了。