繁体   English   中英

为什么界限在Java中如此奇怪?

[英]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.

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