简体   繁体   English

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

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

I've been beating my head against this one for awhile and thought that maybe some fresh eyes will see the issue; 我一直在打击这个问题一段时间,并认为可能会有一些新鲜的眼睛看到这个问题; thanks for your time. 谢谢你的时间。

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

Using Java 8. It looks to me like the direct creation of the container in test is equivalent to the container in test2 , but the compiler says: 使用Java 8.在我看来, test容器的直接创建等同于test2的容器,但编译器说:

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

How do I write Tbin and TbinList so the last line is acceptable? 如何编写TbinTbinList以便最后一行可以接受?

Note that I will actually be adding typed Tbin s which is why I specified Tbin<Derived> in the last line. 请注意,我实际上将添加类型化的Tbin ,这就是我在最后一行指定Tbin<Derived>原因。

This happens because of the way capture conversion works: 这是因为捕获转换的工作方式:

There exists a capture conversion from a parameterized type G<T 1 ,...,T n > to a parameterized type G<S 1 ,...,S n > , where, for 1 ≤ i ≤ n : 存在从参数化类型G<T 1 ,...,T n >到参数化类型G<S 1 ,...,S n >的捕获转换,其中,对于1≤i≤n

  • If T i is a wildcard type argument of the form ? extends B i 如果T i是表单的通配符类型参数? extends B i ? extends B i , then S i is a fresh type variable [...]. ? extends B i ,然后S i是一个新的类型变量[...]。

Capture conversion is not applied recursively. 捕获转换不会递归应用。

Note the end bit. 注意结束位。 So, what this means is that, given a type like this: 所以,这意味着,给定这样的类型:

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

Only "outside" wildcards are captured. 只捕获“外部”通配符。 The Map key wildcard is captured, but the List element wildcard is not. 捕获Map键通配符,但不捕获List元素通配符。 This is why, for example, we can add to a List<List<?>> , but not a List<?> . 这就是为什么,例如,我们可以添加List<List<?>> ,而不是List<?> The placement of the wildcard is what matters. 通配符的位置才是最重要的。

Carrying this over to TbinList , if we have an ArrayList<Tbin<?>> , the wildcard is in a place where it does not get captured, but if we have a TbinList<?> , the wildcard is in a place where it gets captured. 将这个转移到TbinList ,如果我们有一个ArrayList<Tbin<?>> ,通配符就会被捕获,但是如果我们有一个TbinList<?> ,那么通配符就在它所在的地方抓获。

As I alluded to in the comments, one very interesting test is this: 正如我在评论中提到的,一个非常有趣的测试是这样的:

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

We get this error: 我们收到此错误:

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>>

So there's no way to make it work as-is. 所以没有办法让它按原样运作。 One of the class declarations needs to be changed. 其中一个类声明需要更改。


Additionally, think about it this way. 另外,这样考虑一下。

Suppose we had: 假设我们有:

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

And since a wildcard allows subtyping, we can do this: 由于通配符允许子类型化,我们可以这样做:

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

Should we be able to add a Tbin<Derived2> to test4 ? 我们应该能够将Tbin<Derived2>添加到test4吗? No, this would be heap pollution. 不,这将是堆污染。 We might end up with Derived2 s floating around in a TbinList<Derived1> . 我们最终可能会在TbinList<Derived1>浮动Derived2

Replacing the definition of TbinList with 用。替换TbinList的定义

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

and defining test2 with 用。定义test2

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

instead would solve the issue. 相反会解决问题。

With your definition you're ending up with an ArrayList<Tbin<T>> where T is any fixed class extending Base . 根据您的定义,您最终会得到一个ArrayList<Tbin<T>> ,其中T是任何固定类扩展Base

You're using a bounded wildcard ( TbinList<? extends Base>> ... ). 您正在使用有界通配符( TbinList<? extends Base>> ... )。 This wildcard will prevent you from adding any elements to the list. 此通配符将阻止您向列表中添加任何元素。 If you want more info, heres the section about Wildcards in the docs. 如果您想了解更多信息,请参阅文档中有关通配符的部分。

you cannot add any objects to TbinList<? extends Base> 你不能添加任何对象到TbinList<? extends Base> TbinList<? extends Base> ,it is not guaranteed what objects you are inserting into the list. TbinList<? extends Base> ,不保证您要插入列表中的对象。 It is supposed to read data from test2 when you use wildcard extends 当您使用通配符extends时,它应该从test2读取数据

If you declared as TbinList<? extends Base> 如果你声明为TbinList<? extends Base> TbinList<? extends Base> which means you it is any subclass of the class Base or class Base itself, and when you initialize it you use diamond other than concrete class name, it makes your test2 not obvious which makes it harder to tell what objects can be inserted. TbinList<? extends Base>这意味着它是类Base或类Base本身的任何子类,当你初始化它时,你使用钻石而不是具体的类名,它会使你的test2不明显,这使得更难以告诉哪些对象可以插入。 My suggestion is that avoid such declaration it is dangerous, it may not have compile errors but it is horrible code, you might add something, but you also might add the WRONG thing which will break your code. 我的建议是避免这样的声明它是危险的,它可能没有编译错误,但它是可怕的代码,你可能会添加一些东西,但你也可能添加错误的东西会破坏你的代码。

You can define the generic types as follows: 您可以按如下方式定义泛型类型:

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

Then you would create instance like: 然后你会创建如下的实例:

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

OK, here's the answer: 好的,这是答案:

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

  }
}

As I expected, kind of obvious once I saw it. 正如我所料,一旦我看到它,就会显而易见。 But a lot of thrashing around to get here. 但是很多人都在这里挣扎。 Java generics seem simple if you only look at working code. 如果只查看工作代码,Java泛型看起来很简单。

Thanks, everyone, for being a sounding board. 谢谢,大家,作为一个发声板。

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

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