繁体   English   中英

Java Generics Puzzler,扩展一个类并使用通配符

[英]Java Generics Puzzler, extending a class and using wildcards

我一直在打击这个问题一段时间,并认为可能会有一些新鲜的眼睛看到这个问题; 谢谢你的时间。

import java.util.*;

class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}

class Base {}
class Derived extends Base {}

public class Test {
  public static void main(String[] args) {
    ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
    test.add(new Tbin<Derived>());

    TbinList<? extends Base> test2 = new TbinList<>();
    test2.add(new Tbin<Derived>());
  }
}

使用Java 8.在我看来, test容器的直接创建等同于test2的容器,但编译器说:

Test.java:15: error: no suitable method found for add(Tbin<Derived>)
    test2.add(new Tbin<Derived>());
         ^

如何编写TbinTbinList以便最后一行可以接受?

请注意,我实际上将添加类型化的Tbin ,这就是我在最后一行指定Tbin<Derived>原因。

这是因为捕获转换的工作方式:

存在从参数化类型G<T 1 ,...,T n >到参数化类型G<S 1 ,...,S n >的捕获转换,其中,对于1≤i≤n

  • 如果T i是表单的通配符类型参数? extends B i ? extends B i ,然后S i是一个新的类型变量[...]。

捕获转换不会递归应用。

注意结束位。 所以,这意味着,给定这样的类型:

    Map<?, List<?>>
//      │  │    └ no capture (not applied recursively)
//      │  └ T2 is not a wildcard
//      └ T1 is a wildcard

只捕获“外部”通配符。 捕获Map键通配符,但不捕获List元素通配符。 这就是为什么,例如,我们可以添加List<List<?>> ,而不是List<?> 通配符的位置才是最重要的。

将这个转移到TbinList ,如果我们有一个ArrayList<Tbin<?>> ,通配符就会被捕获,但是如果我们有一个TbinList<?> ,那么通配符就在它所在的地方抓获。

正如我在评论中提到的,一个非常有趣的测试是这样的:

ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();

我们收到此错误:

error: incompatible types: cannot infer type arguments for TbinList<>
    ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
                                                        ^
    reason: no instance(s) of type variable(s) T exist so that TbinList<T> conforms to ArrayList<Tbin<? extends Base>>

所以没有办法让它按原样运作。 其中一个类声明需要更改。


另外,这样考虑一下。

假设我们有:

class Derived1 extends Base {}
class Derived2 extends Base {}

由于通配符允许子类型化,我们可以这样做:

TbinList<? extends Base> test4 = new TbinList<Derived1>();

我们应该能够将Tbin<Derived2>添加到test4吗? 不,这将是堆污染。 我们最终可能会在TbinList<Derived1>浮动Derived2

用。替换TbinList的定义

class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

用。定义test2

TbinList<Base> test2 = new TbinList<>();

相反会解决问题。

根据您的定义,您最终会得到一个ArrayList<Tbin<T>> ,其中T是任何固定类扩展Base

您正在使用有界通配符( TbinList<? extends Base>> ... )。 此通配符将阻止您向列表中添加任何元素。 如果您想了解更多信息,请参阅文档中有关通配符的部分。

你不能添加任何对象到TbinList<? extends Base> TbinList<? extends Base> ,不保证您要插入列表中的对象。 当您使用通配符extends时,它应该从test2读取数据

如果你声明为TbinList<? extends Base> TbinList<? extends Base>这意味着它是类Base或类Base本身的任何子类,当你初始化它时,你使用钻石而不是具体的类名,它会使你的test2不明显,这使得更难以告诉哪些对象可以插入。 我的建议是避免这样的声明它是危险的,它可能没有编译错误,但它是可怕的代码,你可能会添加一些东西,但你也可能添加错误的东西会破坏你的代码。

您可以按如下方式定义泛型类型:

class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}

然后你会创建如下的实例:

TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());

好的,这是答案:

import java.util.*;

class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}

class Base {}
class Derived extends Base {}

public class Test {
  public static void main(String[] args) {

    TbinList<Base> test3 = new TbinList<>();
    test3.add(new Tbin<Derived>());

  }
}

正如我所料,一旦我看到它,就会显而易见。 但是很多人都在这里挣扎。 如果只查看工作代码,Java泛型看起来很简单。

谢谢,大家,作为一个发声板。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM