为了账号安全,请及时绑定邮箱和手机立即绑定

试图理解超级通配符在Java泛型中的作用

试图理解超级通配符在Java泛型中的作用

温温酱 2023-11-01 20:57:24
我正在阅读这篇文章来了解通配符在泛型中的作用super。我明白如何extends运作,但我很难理解super我有一个扩展ClassA,使ClassB超ClassA类ClassB。如果我正确理解这篇文章,则将<? super ClassB>允许任何属于ClassB.我有以下代码GenericMethod.javapublic class GenericMethod<T> {    private List<T> list;    public GenericMethod() {        list = new ArrayList<>();    }    public void add(T t) {        list.add(t);    }    public T get(int index) {        return list.get(index);    }}Driver.javapublic class Driver {    public static void main(String[] args) {        GenericMethod<? super ClassB> genericMethod = new GenericMethod<>();        ClassA classA = new ClassA();        genericMethod.add(classA); // Compile-time error here    }}错误The method add(capture#1-of ? super ClassB) in the type GenericMethod<capture#1-of ? super ClassB> is not applicable for the arguments (ClassA)我不明白我哪里错了。当我实例化该类时GenericMethod,我已经声明它将接受任何带有声明的超类型值。因此,类内部应该接受所有扩展的类。ClassB<? super ClassB>TGenericMethodClassB那么为什么该add方法会抛出编译时错误呢?该方法不应该add已经知道它正在传递一个完全兼容的类型吗?
查看完整描述

3 回答

?
一只甜甜圈

TA贡献1836条经验 获得超5个赞

通过声明,GenericMethod<? super ClassB>您将声明该类型是未知类型,它是 ClassB(或 ClassB 本身)的超类。并要求编译器只允许将这种未知类型的子类型添加到列表中。

编译器知道的唯一兼容子类型是 ClassB 和 ClassB 的任何子类型。创建实例时,通常最好避免使用通配符。

对于方法参数,通配符使您可以更灵活地接受哪些内容。使用PECS(生产者=扩展,消费者=超级) 来确定使用哪个。 


查看完整回答
反对 回复 2023-11-01
?
慕妹3146593

TA贡献1820条经验 获得超9个赞

? super子句是下限通配符。但界限是在推断的类型参数上,而不是对可以传递给采用该泛型类型参数的方法的参数类型的限制。

当您说 时<? super ClassB>,您表明类型参数可以是ClassB或 任何超类型,例如ClassAObject

编译器必须将该add方法视为可以是以下任何签名:

add(Object t)
add(ClassA t)
add(ClassB t)

ClassA(如果直接从另一个类继承而不是继承的话,可能还有其他类型Object)。

编译器必须拒绝将 aClassA作为 的参数,add因为类型参数可能被推断为ClassBGenericMethod<ClassB>将 a 分配给变量是合法的genericMethod

GenericMethod<? super ClassB> genericMethod = new GenericMethod<ClassB>();

但是能够将 a 传递ClassA给需要 a 的方法是没有意义的ClassB

事实上,这就是菱形算子 - 的推断ClassB

您的困惑在于两个概念的合并:允许哪些类型参数以及使用类型参数的方法中允许哪些类型的对象。使用通配符会限制类型参数,但该方法仍然接受类型参数或子类型的类型。


查看完整回答
反对 回复 2023-11-01
?
慕桂英3389331

TA贡献2036条经验 获得超8个赞

我承认这有点违反直觉,但从编译器的角度来看,这是有道理的。

假设您以这种方式定义一个列表:

List<? super ClassB> myList;

你得到了

ClassB extends ClassA;
ClassC extends ClassA;

你能做吗?

ClassA a = new ClassC();
myList.add (a);//does not compile

不,有几个原因。首先是因为最终在运行时,参数化类型变成了具体的单一类型。因此,实际上您不能将A 类对象与 B 类对象混合。

其次,因为编译器必须检查您插入的任何内容是否可强制转换为ClassB。因此,只需添加一个 ClassB 对象(或他的子类型),即可履行合同。但事实并非总是如此!ClassA 对象不能是 ClassB 的超类型。

即对象“a”实际上是一个ClassC对象!

这就是为什么你不能添加超类型。所以它只允许您添加 ClassB 对象 - 或子类型 -

如果将对象“a”转换为 ClassB,则会编译,但在运行时会失败并出现ClassCastException

ClassA a = new ClassC();
myList.add((ClassB) a); //Compiles but fails at runtime

同时,通配符与super结合,可以作为方法的形参。IE:

void printValues (List<? super ClassB> list)

这种方式允许对超级 B 类集合的处理进行一般化,从而使以下调用成为可能:

List<Object> lo;
List<ClassA> la;
...
printValues (lo);
printValues (la);

在本例中,定义使用通配符,例如 <? super ClassB>,更有意义一些。


查看完整回答
反对 回复 2023-11-01
  • 3 回答
  • 0 关注
  • 99 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信