< 和有啥区别?扩展基础> 和 <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 解释说,唯一的区别是,与<? ...>
不同,<T ...>
允许您稍后引用该类型,但似乎并非如此。
在这种情况下<? extends Number>
和<T extends Number>
有什么区别,为什么第一个编译不出来?
【问题讨论】:
评论不用于扩展讨论;这个对话是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<Integer, List<Integer>> map)
。
说到doesntCompile
方法,jls定义了子类型化规则(§4.5.1,我加粗了):
如果 T2 表示的类型集在自反和传递闭包下可证明是 T1 表示的类型集的子集,则称一个类型参数 T1 包含另一个类型参数 T2,写作 T2
?扩展 T 则扩展 S
?扩展 T
?超级 T
?超级 T
?超级 T
T
T
T
这意味着,? extends Number
确实包含Integer
,甚至List<? extends Number>
包含List<Integer>
,但Map<Integer, List<? extends Number>>
和Map<Integer, List<Integer>>
并非如此。有关该主题的更多信息,请访问in this SO thread。您仍然可以通过声明您期望List<? extends Number>
的子类型来使带有?
通配符的版本工作:
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> 和 Liststatic void demo(List<? extends Number> lst)
并像这样调用它demo(new ArrayList<Integer>());
或这个demo(new ArrayList<Float>());
,代码编译并运行正常。还是我可能误读或误解了您所说的内容?
@你在这两种情况下都是对的。关于你的第二点,我以一种误导的方式写了它。我的意思是 List<? extends Number>
作为整个地图的类型参数,而不是它本身。非常感谢您的评论。
@skomisa 出于同样的原因List<Number>
不包含List<Integer>
。假设你有一个函数static void check(List<Number> numbers)
。当使用check(new ArrayList<Integer>());
调用时,它不会编译,您必须将方法定义为static void check(List<? extends Number> numbers)
。与地图相同,但嵌套更多。
@skomisa 就像Number
是list 的类型参数,需要加上? extends
使其协变,List<? extends Number>
是Map
的类型参数,也需要@987654355 @ 表示协方差。
好的。由于您提供了多级通配符(又名“嵌套通配符”?)的解决方案,并链接到相关的 JLS 参考资料,因此获得了赏金。【参考方案2】:
在通话中:
compiles(new HashMap<Integer, List<Integer>>());
T 与 Integer 匹配,因此参数的类型是 Map<Integer,List<Integer>>
。方法doesntCompile
的情况并非如此:无论调用中的实际参数是什么,参数的类型都保持Map<Integer, List<? extends Number>>
;这不能从HashMap<Integer, List<Integer>>
分配。
更新
在doesntCompile
方法中,没有什么能阻止你做这样的事情:
static void doesntCompile(Map<Integer, List<? extends Number>> map)
map.put(1, new ArrayList<Double>());
很明显,它不能接受HashMap<Integer, List<Integer>>
作为参数。
【讨论】:
那么对doesntCompile
的有效调用会是什么样子呢?只是好奇。
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());
会起作用,doesntCompile(new HashMap<>());
也会起作用。
@XtremeBiker,即使这样也行,MapHashMap<Integer, List<Integer>>
" 你能详细说明为什么它不能从它分配吗?
这和static void demo(List<? extends Number> 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<Pair<? extends Number>>
是多级通配符类型,而List<? extends Number>
是标准通配符类型。
通配符类型List<? extends Number>
的有效具体实例包括Number
和Number
的任何子类型,而在List<Pair<? extends Number>>
的情况下,它是类型参数的类型参数并且本身具有泛型类型的具体实例.
泛型是不变的,所以Pair<? extends Number>
通配符类型只能接受Pair<? extends Number>>
。内部类型? extends Number
已经是协变的。您必须将封闭类型设为协变以允许协变。
【讨论】:
为什么<Pair<Integer>>
不能与<Pair<? extends Number>>
一起使用,但可以与<T extends Number> <Pair<T>>
一起使用?
@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<Double>
和List<Integer>
一样绝对没问题,对吧?
但是,您仍然认为在您的示例中传递 new HashMap<Integer, List<Integer>>()
是合法吗?
编译器不这么认为,并且正在尽力避免这种情况。
尝试使用方法#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<T>
,这就是为什么用 new HashMap<Integer, List<Integer>>()
或 new HashMap<Integer, List<Double>>()
或 new HashMap<Integer, List<Long>>()
或 new HashMap<Integer, List<Number>>()
。
因此,简而言之,您是在尝试使用编译器作弊,它可以公平地防御这种作弊。
注意:Maurice Perry 发布的答案绝对正确。我只是不确定它是否足够清楚,所以尝试(真的希望我设法)添加更广泛的帖子。
【讨论】:
以上是关于< 和有啥区别?扩展基础> 和 <T 扩展基础>?的主要内容,如果未能解决你的问题,请参考以下文章