< 和有啥区别?扩展基础> 和 <T 扩展基础>?

Posted

技术标签:

【中文标题】< 和有啥区别?扩展基础> 和 <T 扩展基础>?【英文标题】:What is the difference between <? extends Base> and <T extends Base>?< 和有什么区别?扩展基础> 和 <T 扩展基础>? 【发布时间】:2020-06-17 13:23:57 【问题描述】:

在这个例子中:

import java.util.*;

public class Example 
    static void doesntCompile(Map<Integer, List<? extends Number>> map) 
    static <T extends Number> void compiles(Map<Integer, List<T>> map) 

    static void function(List<? extends Number> outer)
    
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    

doesntCompile() 编译失败:

Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
        doesntCompile(new HashMap<Integer, List<Integer>>());
                      ^

compiles() 被编译器接受。

This answer 解释说,唯一的区别是,与&lt;? ...&gt; 不同,&lt;T ...&gt; 允许您稍后引用该类型,但似乎并非如此。

在这种情况下&lt;? extends Number&gt;&lt;T extends Number&gt; 有什么区别,为什么第一个编译不出来?

【问题讨论】:

评论不用于扩展讨论;这个对话是moved to chat。 [1] Java Generic type : difference between List <? extends Number> and List <T extends Number> 似乎在问同样的问题,但虽然它可能很有趣,但它并不是真正的重复。 [2] 虽然这是一个很好的问题,但标题并不能正确反映最后一句中提出的具体问题。 这里已经回答了Java Generic List<List<? extends Number>> here的解释如何? 【参考方案1】:

通过使用以下签名定义方法:

static <T extends Number> void compiles(Map<Integer, List<T>> map) 

并像这样调用它:

compiles(new HashMap<Integer, List<Integer>>());

您正在将 T 与您提供的类型相匹配。

在 jls §8.1.2 中,我们发现(有趣的部分由我加粗):

泛型类声明定义了一组参数化类型(第 4.5 节),类型参数对类型参数部分的每个可能调用都有一个。所有这些参数化类型在运行时共享同一个类。

换句话说,T 类型与输入类型匹配并分配了Integer。签名将有效地变为static void compiles(Map&lt;Integer, List&lt;Integer&gt;&gt; map)

说到doesntCompile方法,jls定义了子类型化规则(§4.5.1,我加粗了):

如果 T2 表示的类型集在自反和传递闭包下可证明是 T1 表示的类型集的子集,则称一个类型参数 T1 包含另一个类型参数 T2,写作 T2

?扩展 T 则扩展 S

?扩展 T

?超级 T

?超级 T

?超级 T

T

T

T

这意味着,? extends Number 确实包含Integer,甚至List&lt;? extends Number&gt; 包含List&lt;Integer&gt;,但Map&lt;Integer, List&lt;? extends Number&gt;&gt;Map&lt;Integer, List&lt;Integer&gt;&gt; 并非如此。有关该主题的更多信息,请访问in this SO thread。您仍然可以通过声明您期望List&lt;? extends Number&gt; 的子类型来使带有? 通配符的版本工作:

public class Example 
    // now it compiles
    static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) 
    static <T extends Number> void compiles(Map<Integer, List<T>> map) 

    public static void main(String[] args) 
        doesntCompile(new HashMap<Integer, List<Integer>>());
        compiles(new HashMap<Integer, List<Integer>>());
    

【讨论】:

[1] 我认为您的意思是 ? extends Number 而不是 ? extends Numeric。 [2] 您关于 "不是 List extends Number> 和 List" 的断言是不正确的。正如@VinceEmigh 已经指出的那样,您可以创建一个方法static void demo(List&lt;? extends Number&gt; lst) 并像这样调用它demo(new ArrayList&lt;Integer&gt;()); 或这个demo(new ArrayList&lt;Float&gt;());,代码编译并运行正常。还是我可能误读或误解了您所说的内容? @你在这两种情况下都是对的。关于你的第二点,我以一种误导的方式写了它。我的意思是 List&lt;? extends Number&gt; 作为整个地图的类型参数,而不是它本身。非常感谢您的评论。 @skomisa 出于同样的原因List&lt;Number&gt; 不包含List&lt;Integer&gt;。假设你有一个函数static void check(List&lt;Number&gt; numbers) 。当使用check(new ArrayList&lt;Integer&gt;()); 调用时,它不会编译,您必须将方法定义为static void check(List&lt;? extends Number&gt; numbers) 。与地图相同,但嵌套更多。 @skomisa 就像Number 是list 的类型参数,需要加上? extends 使其协变,List&lt;? extends Number&gt;Map 的类型参数,也需要@987654355 @ 表示协方差。 好的。由于您提供了多级通配符(又名“嵌套通配符”?)的解决方案,并链接到相关的 JLS 参考资料,因此获得了赏金。【参考方案2】:

在通话中:

compiles(new HashMap<Integer, List<Integer>>());

T 与 Integer 匹配,因此参数的类型是 Map&lt;Integer,List&lt;Integer&gt;&gt;。方法doesntCompile 的情况并非如此:无论调用中的实际参数是什么,参数的类型都保持Map&lt;Integer, List&lt;? extends Number&gt;&gt;;这不能从HashMap&lt;Integer, List&lt;Integer&gt;&gt; 分配。

更新

doesntCompile 方法中,没有什么能阻止你做这样的事情:

static void doesntCompile(Map<Integer, List<? extends Number>> map) 
    map.put(1, new ArrayList<Double>());

很明显,它不能接受HashMap&lt;Integer, List&lt;Integer&gt;&gt; 作为参数。

【讨论】:

那么对doesntCompile 的有效调用会是什么样子呢?只是好奇。 @XtremeBiker doesntCompile(new HashMap&lt;Integer, List&lt;? extends Number&gt;&gt;()); 会起作用,doesntCompile(new HashMap&lt;&gt;()); 也会起作用。 @XtremeBiker,即使这样也行,Map> map = new HashMap>(); map.put(null, new ArrayList());不编译(地图); "that is notassignable from HashMap&lt;Integer, List&lt;Integer&gt;&gt;" 你能详细说明为什么它不能从它分配吗? 这和static void demo(List&lt;? extends Number&gt; list)有什么区别?你也不能在里面做list.add(new Double(0.0)),但是它不能在demo里面编译,而不是像doesntCompile那样在外面编译。【参考方案3】:

演示的简单示例。同样的例子可以如下图所示。

static void demo(List<Pair<? extends Number>> lst)  // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst)  // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too

public static class Pair<T> 
public static class SubPair<T> extends Pair<T> 

List&lt;Pair&lt;? extends Number&gt;&gt; 是多级通配符类型,而List&lt;? extends Number&gt; 是标准通配符类型。

通配符类型List&lt;? extends Number&gt; 的有效具体实例包括NumberNumber 的任何子类型,而在List&lt;Pair&lt;? extends Number&gt;&gt; 的情况下,它是类型参数的类型参数并且本身具有泛型类型的具体实例.

泛型是不变的,所以Pair&lt;? extends Number&gt; 通配符类型只能接受Pair&lt;? extends Number&gt;&gt;。内部类型? extends Number 已经是协变的。您必须将封闭类型设为协变以允许协变。

【讨论】:

为什么&lt;Pair&lt;Integer&gt;&gt; 不能与&lt;Pair&lt;? extends Number&gt;&gt; 一起使用,但可以与&lt;T extends Number&gt; &lt;Pair&lt;T&gt;&gt; 一起使用? @jaco0646 您本质上是在问与 OP 相同的问题,the answer from Andronicus 已被接受。请参阅该答案中的代码示例。 @skomisa,是的,我问同样的问题有几个原因:一个是这个答案实际上似乎并没有解决 OP 的问题;但二是我觉得这个答案更容易理解。我无法以任何方式遵循 Andronicus 的答案,让我理解嵌套与非嵌套泛型,甚至 T?。部分问题在于,当 Andronicus 到达他的解释的关键点时,他会顺从另一个只使用琐碎示例的线程。我希望在这里得到一个更清晰、更完整的答案。 @jaco0646 好的。 Angelika Langer 的文档“Java 泛型常见问题解答 - 类型参数” 有一个标题为 What do multi-level (i.e., nested) wildcards mean? 的常见问题解答。这是我所知道的用于解释 OP 问题中提出的问题的最佳来源。嵌套通配符的规则既不简单也不直观。【参考方案4】:

我建议你看看documentation of generic wildcards,尤其是guidelines for wildcard use

坦率地说你的方法#doesntCompile

static void doesntCompile(Map<Integer, List<? extends Number>> map) 

然后像打电话一样

doesntCompile(new HashMap<Integer, List<Integer>>());

根本不正确

让我们添加合法实施:

    static void doesntCompile(Map<Integer, List<? extends Number>> map) 
        List<Double> list = new ArrayList<>();
        list.add(0.);
        map.put(0, list);
    

真的很好,因为Double扩展了Number,所以放List&lt;Double&gt;List&lt;Integer&gt;一样绝对没问题,对吧?

但是,您仍然认为在您的示例中传递 new HashMap&lt;Integer, List&lt;Integer&gt;&gt;()合法吗?

编译器不这么认为,并且正在尽力避免这种情况。

尝试使用方法#compile 执行相同的实现,编译器显然不允许您将双精度列表放入映射中。

    static <T extends Number> void compiles(Map<Integer, List<T>> map) 
        List<Double> list = new ArrayList<>();
        list.add(10.);
        map.put(10, list); // does not compile
    

基本上你只能放List&lt;T&gt;,这就是为什么用 new HashMap&lt;Integer, List&lt;Integer&gt;&gt;()new HashMap&lt;Integer, List&lt;Double&gt;&gt;()new HashMap&lt;Integer, List&lt;Long&gt;&gt;()new HashMap&lt;Integer, List&lt;Number&gt;&gt;()

因此,简而言之,您是在尝试使用编译器作弊,它可以公平地防御这种作弊。

注意:Maurice Perry 发布的答案绝对正确。我只是不确定它是否足够清楚,所以尝试(真的希望我设法)添加更广泛的帖子。

【讨论】:

以上是关于< 和有啥区别?扩展基础> 和 <T 扩展基础>?的主要内容,如果未能解决你的问题,请参考以下文章

.* 和有啥不一样?和 .* 正则表达式?

javascript函数中的()和有啥区别? [复制]

CRC和校验和有啥区别?

`|_| 和有啥区别?异步移动 ` 和 `异步移动 |_| `

Java泛型中的“超级”和“扩展”有啥区别[重复]

c++template中typename 和class有啥区别?