我一直在反对这一个人,并认为可能会有一些新鲜的眼睛看到这个问题;谢谢你的时间。
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>());
^
如何撰写Tbin
和TbinList
,以便最后一行可以接受?
请注意,我实际上会添加类型Tbin
,这就是我在最后一行指定Tbin<Derived>
的原因。
答案 0 :(得分:37)
这是因为capture conversion的工作方式:
存在从参数化类型
G<T1,...,Tn>
到参数化类型G<S1,...,Sn>
的捕获转换,其中,对于1≤i≤n:
- 如果
Ti
是? extends Bi
形式的通配符类型参数,则Si
是一个新的类型变量[...]。不会递归地应用捕获转换。
注意结束位。所以,这意味着,给定这样的类型:
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
吗?不,这将是堆污染。我们可能最终会在Derived2
中浮动TbinList<Derived1>
。
答案 1 :(得分:10)
用
替换TbinList
的定义
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
并使用
定义test2
TbinList<Base> test2 = new TbinList<>();
相反会解决问题。
根据您的定义,您最终得到ArrayList<Tbin<T>>
,其中T是任何延伸Base
的固定类。
答案 2 :(得分:3)
您使用的是有界通配符(TbinList<? extends Base>> ...
)。此通配符将阻止您向列表中添加任何元素。如果您想了解更多信息,请参阅文档中有关Wildcards的部分。
答案 3 :(得分:1)
您无法向TbinList<? extends Base>
添加任何对象,但无法保证您在列表中插入了哪些对象。当您使用通配符test2
extends
读取数据
如果你声明为TbinList<? extends Base>
,这意味着它是类Base或类Base本身的任何子类,并且在初始化它时使用钻石而不是具体的类名,它会使你test2
不明显,这使得更难分辨哪些对象可以插入。我的建议是避免这样的声明它是危险的,它可能没有编译错误,但它是可怕的代码,你可能会添加一些东西,但你也可能添加错误的东西会破坏你的代码。
答案 4 :(得分:1)
您可以按如下方式定义泛型类型:
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>());
答案 5 :(得分:1)
好的,这是答案:
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泛型看起来很简单。
谢谢大家,感谢他们成为一名发声板。