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

时间:2016-10-21 12:17:45

标签: java generics exception super ocpjp

我正在使用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());
    }
};

但是在下一个代码中,当我们添加 new A()时,编译错误:

list3.add(new A()); // compilation error
list3.add(new B());
list3.add(new C());

感谢您的回答!

2 个答案:

答案 0 :(得分:3)

这是一个旨在强制执行类型安全的编译错误。如果编译器允许你这样做,想象一下会发生什么:

对于问题1,一旦声明了对象list1,编译器只会考虑声明的类型,即List<?>,并忽略它最近被分配给{{1}的事实。 }。

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>的初始化块。没关系。

第二个问题(与list2)有相同的原因。

在第三期中,

add(new A())

编译器将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>或其超类B。如果它是A怎么办?您无法将List<B>添加到A;因此编译器拒绝此代码。

答案 1 :(得分:1)

简短的回答(“为什么它很奇怪”)是某些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());的简写。在SpecialList()构造函数中,this是SpecialList类型的引用 - 声明为扩展ArrayList<A> - 当然我们可以add(new A())ArrayList<A>的子类。< / p>

所以现在我们已经完成了与原始代码类似的所有工作:

List<?> list1 = new ArrayList<A>() {
    {
        add(new A());
    }
}
list1.add(new A());

由于我们应用的Java语法糖,现在第3行和第6行看起来非常相似。但第3行与我们的SpecialList()示例非常相似 - 调用add()的引用类型是ArrayList<A>的匿名子类。但是,第6行几乎就是它的样子 - 因此它失败的原因与前几个例子中的原因相同。

类似的分析将解释你所看到的其他奇怪的区别。