[英]Why bounds work so strange in Java?
我正在使用Java 8.在通过Java OCP 8的培训期间,我发现了一些我不理解和想知道的代码片段,为什么它对我来说如此奇怪。
我有下一个层次结构:
class A {}
class B extends A {}
class C extends B {}
第一个,这个代码是工作:
List<?> list1 = new ArrayList<A>() {
{
add(new A());
}
};
但是下一个代码不起作用,编译错误:
list1.add(new A());
那么,为什么我们不能以这种方式添加新记录呢?
第二个,这个代码是工作:
List<? extends A> list2 = new ArrayList<A>() {
{
add(new A());
add(new B());
add(new C());
}
};
但是下一个代码不起作用,编译错误:
list2.add(new A());
list2.add(new B());
list2.add(new C());
最后一个,这个代码是工作:
List<? super B> list3 = new ArrayList<A>() {
{
add(new A());
add(new B());
add(new C());
}
};
但是在下一个代码中,当我们添加新的A()时 ,编译错误:
list3.add(new A()); // compilation error
list3.add(new B());
list3.add(new C());
谢谢你的回答!
这是一个旨在强制执行类型安全的编译错误。 如果编译器允许你这样做,想象一下会发生什么:
对于问题1,一旦声明了对象list1
,编译器只考虑声明的类型,即List<?>
并忽略它最近被分配给ArrayList<A>
的事实。
List<?> list1 = ...; // The compiler knows list1 is a list of a certain type
// but it's not specified what the type is. It could be
// a List<String> or List<Integer> or List<Anything>
list1.add(new A()); // What if list1 was e.g. a List<String>?
但:
List<?> list1 = new ArrayList<A>() {
{
add(new A());
}
};
在这里,您将为list1
分配一个表达式。 表达式本身,即=
之后的所有内容,都不使用?
,实际上是一个扩展ArrayList<A>
的匿名类,并有一个初始化块,调用add(new A())
,这是好的。
第二个问题(与list2)有相同的原因。
在第三期,
List<? super B> list3 = new ArrayList<A>() {
{
add(new A());
add(new B());
add(new C());
}
};
list3.add(new A()); // compilation error
编译器将list3
视为List<? super B>
List<? super B>
。 这意味着泛型参数可以是B
或其超类A
如果它是List<B>
怎么办? 您无法将A
添加到List<B>
; 因此编译器拒绝此代码。
简短的回答(“为什么它很奇怪”)是某些Java简写符号使两段代码看起来非常相似,当它们真的非常不同时。
所以看起来“如果这样做,这也应该有效”,但事实并非如此,因为两位代码中的重要差异是模糊的。
让我们分别看几段代码:
public interface ListFactory {
List<?> getList();
}
所以有人会给我们一个请求List<?>
。 我们可以像这样使用它:
List<?> list1 = myListFactory.getList();
但如果我们这样做
list1.add(new A());
编译器无法证明这是合法的,因为它取决于我们是否碰巧得到一个返回List<A>
或List<String>
的ListFactory实现。
如果我们用以上代替上述内容怎么办
List<?> list1 = new SpecialList();
list1.add(new A());
这比原始代码更接近; 但是对于编译器存在同样的问题:当它评估list1.add(new A());
它不会在list1的赋值历史中寻找线索。 它只知道list1的编译时引用类型是List<?>
,所以如果list1.add(new A());
以前是非法的,在这里仍然是非法的。
(乍一看,这可能感觉像是编译器的一个缺点,但我认为不是;编译器通常不会尝试和了解直接告诉它的参考类型,这通常是不实际或不可取的。如果我们想要的话以允许add(new A())
的方式引用我们的对象,我们使用不同的引用类型 - 例如List<A> list2 = new SpecialList();
)
以上暗示我们有一个SpecialList.java; 让我们来看看它:
public class SpecialList extends ArrayList<A> {
public SpecialList() {
add(new A());
}
}
这可能看起来有些愚蠢,但就编译器而言,只要定义了A并导入了java.util.ArrayList,它就不足为奇了。
注意add(new A());
在这种情况下, this.add(new A());
简写this.add(new A());
。 在SpecialList()构造函数中, this
是一个类型为SpecialList的引用 - 它被声明为扩展ArrayList<A>
- 当然我们可以add(new A())
到ArrayList<A>
的子类中。
所以现在我们有了所有的东西来制作像你的原始代码:
List<?> list1 = new ArrayList<A>() {
{
add(new A());
}
}
list1.add(new A());
由于我们应用的Java语法糖,现在第3行和第6行看起来非常相似。 但第3行与我们的SpecialList()示例非常相似 - 调用add()的引用类型是ArrayList<A>
的匿名子类。 然而,第6行几乎就是它的样子 - 因此它失败的原因与它在前几个例子中所做的相同。
类似的分析将解释你所看到的其他奇怪的区别。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.