<和有什么区别?扩展Base>和<T扩展Base>?


29

在此示例中:

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()被编译器接受。

该答案说明,唯一的区别是与不同<? ...>,它<T ...>允许您稍后引用类型,似乎并非如此。

是什么区别<? extends Number>,并<T extends Number>在这种情况下,为什么不第一编译?


评论不作进一步讨论;此对话已转移至聊天
塞缪尔·柳

[1] Java泛型类型:List <?之间的区别 扩展Number>和List <T扩展Number>似乎在问相同的问题,但是虽然可能感兴趣,但实际上并不是重复的。[2]虽然这是一个很好的问题,但标题并未正确反映最后一句中提出的具体问题。
Skomisa


这里的解释如何?
jrook

Answers:


14

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

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

并像这样调用它:

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

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

通用类声明定义了一组参数化类型(第4.5节),每种可能通过类型arguments调用类型参数节的类型。所有这些参数化类型在运行时共享同一类。

换句话说,将类型T与输入类型进行匹配并进行分配Integer。签名将有效地变为static void compiles(Map<Integer, List<Integer>> map)

关于doesntCompile方法,jls定义了子类型化规则(第4.5.1节,由我加粗):

如果在以下规则的自反和传递闭包中,证明T2表示的类型集是T1表示的类型集的子集,则类型自变量T1包含另一个类型自变量T2,写为T2 <= T1。其中<:表示子类型(§4.10)):

  • ?扩展T <=?如果T <:S则扩展S

  • ?扩展T <=?

  • ?超级T <=?如果S <:T则为超级S

  • ?超级T <=?

  • ?超级T <=?扩展对象

  • T <= T

  • T <=?延伸T

  • T <=?超级T

这意味着,? extends Number确实包含Integer甚至List<? extends Number>包含List<Integer>,但Map<Integer, List<? extends Number>>and 并非如此Map<Integer, List<Integer>>在该SO线程中可以找到关于该主题的更多信息。您仍然可以?通过声明期望使用以下子类型来使带有通配符的版本起作用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 <?扩展Number>和List <Integer>的情况”是不正确的。正如@VinceEmigh指出的那样,您可以创建一个方法static void demo(List<? extends Number> lst) { }并像this demo(new ArrayList<Integer>());或this 那样调用它demo(new ArrayList<Float>());,然后代码编译并运行OK。还是我可能是误读或误解了你所说的话?
Skomisa

@在两种情况下你都是对的。关于您的第二点,我写的是一种误导性的方式。我的意思是List<? extends Number>作为整个地图的类型参数,而不是本身。非常感谢您的评论。
Andronicus

基于相同原因的@skomisa List<Number>不包含List<Integer>。假设您有一个功能static void check(List<Number> numbers) {}。调用时check(new ArrayList<Integer>());无法编译,则必须将方法定义为static void check(List<? extends Number> numbers) {}。与地图相同,但嵌套更多。
Andronicus

1
@skomisa就像Numberlist的类型参数一样,您需要添加? extends使其协变,List<? extends Number>是@的类型参数,Map也需要? extends协变。
Andronicus

1
好。由于您提供了多级通配符(又名“嵌套通配符”?)的解决方案,并且已链接到相关的JLS参考,因此可得到赏金。
Skomisa

6

在通话中:

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

很明显,它不能接受a HashMap<Integer, List<Integer>>作为参数。


那么,有效的通话doesntCompile会是什么样的?对此感到好奇。
Xtreme Biker

1
@XtremeBiker doesntCompile(new HashMap<Integer, List<? extends Number>>());可以正常工作doesntCompile(new HashMap<>());
Skomisa

@XtremeBiker,即使这样也可以,Map <Integer,List <?扩展Number >> map = new HashMap <Integer,List <?扩展Number >>(); map.put(null,new ArrayList <Integer>()); nottCompile(map);
MOnkey

“那是不可分配的HashMap<Integer, List<Integer>>”,您能详细说明一下为什么它不可分配吗?
开发Dev Null

@DevNull见上我的更新
莫里斯·佩里

2

演示的简化示例。相同的示例可以如下所示。

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

@ jaco0646您本质上是在问与OP相同的问题,并且Andronicus的答案已被接受。请参阅该答案中的代码示例。
Skomisa

@skomisa,是的,出于几个原因,我要问同样的问题:一个是这个答案似乎并没有解决OP的问题;但是二是我发现这个答案更容易理解。我无法以任何使我了解嵌套与非嵌套泛型,甚至Tvs的方式来了解Andronicus的答案?。问题的一部分是,当安德罗尼克斯到达他的解释要点时,他顺应了另一个仅使用琐碎例子的话题。我希望在这里得到一个更清晰,更完整的答案。
jaco0646

1
@ jaco0646好。Angelika Langer 撰写的文档“ Java泛型常见问题解答-类型参数”中有一个常见问题解答,标题为多级(即嵌套)通配符是什么意思?。这是我所知的最佳信息,可用于解释《 OP》中提出的问题。嵌套通配符的规则既不直观也不直观。
Skomisa

1

我建议您查看通用通配符的文档,尤其是通配符使用准则

坦白地说,您的方法#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,所以put List<Double>也绝对好List<Integer>,对吗?

但是,您仍然认为从您的示例中通过此处是合法的new HashMap<Integer, List<Integer>>()吗?

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

尝试使用#compile方法执行相同的实现,并且显然编译器将不允许您将double列表放入map中。

    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>>()or new HashMap<Integer, List<Double>>()new HashMap<Integer, List<Long>>()or 调用该方法是安全的new HashMap<Integer, List<Number>>()

简而言之,您正在尝试使用编译器作弊,并且它相当防御这种作弊。

注意:莫里斯·佩里Maurice Perry)发表的答案是绝对正确的。我只是不确定它是否足够清晰,因此尝试(真的希望我设法)增加了更多文章。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.