简体   繁体   English

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

[英]Why bounds work so strange in Java?

I'm using Java 8. During training to passing Java OCP 8 I find some snippets of code that I don't understand and want to know, why it so strange for me. 我正在使用Java 8.在通过Java OCP 8的培训期间,我发现了一些我不理解和想知道的代码片段,为什么它对我来说如此奇怪。

I have next hierarchy: 我有下一个层次结构:

class A {}
class B extends A {}
class C extends B {}

The first one, this code is work: 一个,这个代码是工作:

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

But next code doesn't work, compilation error: 但是下一个代码不起作用,编译错误:

list1.add(new A());

So, why we can't add new record in this way? 那么,为什么我们不能以这种方式添加新记录呢?

The second one, this code is work: 第二个,这个代码是工作:

List<? extends A> list2 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    } 
};

But next code doesn't work, compilation error: 但是下一个代码不起作用,编译错误:

list2.add(new A());
list2.add(new B());
list2.add(new C());

And the last one, this code is work: 最后一个,这个代码是工作:

List<? super B> list3 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    }
};

But in the next code, when we adding new A() , compilation error: 但是在下一个代码中,当我们添加新的A()时 ,编译错误:

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

Thanks for your answers! 谢谢你的回答!

This is a compilation error designed to enforce type safety. 这是一个旨在强制执行类型安全的编译错误。 If the compiler allowed you to do it, imagine what could happen: 如果编译器允许你这样做,想象一下会发生什么:

For issue 1, once the object list1 has been declared, the compiler only considers the declared type, which is List<?> and ignores the fact that it was most recently assigned to an ArrayList<A> . 对于问题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>?

But: 但:

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

Here, you are assigning to list1 an expression. 在这里,您将为list1分配一个表达式。 The expression itself, ie everything after = , doesn't use ? 表达式本身,即=之后的所有内容,都不使用? , and is in fact an anonymous class that extends ArrayList<A> and has an initializer block that calls add(new A()) which is ok. ,实际上是一个扩展ArrayList<A>的匿名类,并有一个初始化块,调用add(new A()) ,这是好的。

The second issue (with list2) has the same cause. 第二个问题(与list2)有相同的原因。

In the third issue, 在第三期,

List<? super B> list3 = new ArrayList<A>() {
    {
        add(new A());
        add(new B());
        add(new C());
    }
};

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

The compiler sees list3 as a List<? super B> 编译器将list3视为List<? super B> List<? super B> . List<? super B> This means the generic parameter can be B or its superclass, A . 这意味着泛型参数可以是B或其超类A What if it's a List<B> ? 如果它是List<B>怎么办? You can't add an A to a List<B> ; 您无法将A添加到List<B> ; therefore the compiler rejects this code. 因此编译器拒绝此代码。

The short answer ("why it is strange") is that certain Java short-hand notations make two pieces of code look very similar when they are really very different. 简短的回答(“为什么它很奇怪”)是某些Java简写符号使两段代码看起来非常相似,当它们真的非常不同时。

So it can seem like "if this works, this should also work", but it's not so because important differences in the two bits of code are obscured. 所以看起来“如果这样做,这也应该有效”,但事实并非如此,因为两位代码中的重要差异是模糊的。

Let's look at a few pieces of code separately: 让我们分别看几段代码:

public interface ListFactory {
    List<?> getList();
}

So someone's going to give us a way to request a List<?> . 所以有人会给我们一个请求List<?> We can use it like this: 我们可以像这样使用它:

List<?> list1 = myListFactory.getList();

But if we do 但如果我们这样做

list1.add(new A());

the compiler can't prove this is legal, because it depends on whether we happened to get a ListFactory implementation that returns a List<A> , or maybe a List<String> . 编译器无法证明这是合法的,因为它取决于我们是否碰巧得到一个返回List<A>List<String>的ListFactory实现。

What if we replace the above with 如果我们用以上代替上述内容怎么办

List<?> list1 = new SpecialList();
list1.add(new A());

This is a little closer to your original code; 这比原始代码更接近; but to the compiler the same problem exists: when it evaluates list1.add(new A()); 但是对于编译器存在同样的问题:当它评估list1.add(new A()); it doesn't look for clues in the history of assignments to list1. 它不会在list1的赋值历史中寻找线索。 It only knows that the compile-time reference type of list1 is List<?> , so if list1.add(new A()); 它只知道list1的编译时引用类型是List<?> ,所以如果list1.add(new A()); was illegal before, it's still illegal here. 以前是非法的,在这里仍然是非法的。

(At a glance this may feel like a shortcoming of the compiler, but I think it's not; it generally isn't practical or desirable for the compiler to try and know any more than the reference type directly tells it. If we'd wanted to refer to our object in a way that allows add(new A()) we'd use a different reference type - eg List<A> list2 = new SpecialList(); .) (乍一看,这可能感觉像是编译器的一个缺点,但我认为不是;编译器通常不会尝试和了解直接告诉它的参考类型,这通常是不实际或不可取的。如果我们想要的话以允许add(new A())的方式引用我们的对象,我们使用不同的引用类型 - 例如List<A> list2 = new SpecialList();

The above implies that we have a SpecialList.java; 以上暗示我们有一个SpecialList.java; let's take a look at it: 让我们来看看它:

public class SpecialList extends ArrayList<A> {
    public SpecialList() {
        add(new A());
    }
}

This might seem a little silly, but it's probably no surprise that there's nothing wrong with it as far as the compiler is concerned (as long as A is defined and java.util.ArrayList is imported). 这可能看起来有些愚蠢,但就编译器而言,只要定义了A并导入了java.util.ArrayList,它就不足为奇了。

Note that add(new A()); 注意add(new A()); in this case is shorthand for this.add(new A()); 在这种情况下, this.add(new A());简写this.add(new A()); . In the SpecialList() constructor, this is a reference of type SpecialList - which is declared to extend ArrayList<A> - and certainly we can add(new A()) to a subclass of ArrayList<A> . 在SpecialList()构造函数中, this是一个类型为SpecialList的引用 - 它被声明为扩展ArrayList<A> - 当然我们可以add(new A())ArrayList<A>的子类中。

So now we have all the pieces to make something like your original code: 所以现在我们有了所有的东西来制作像你的原始代码:

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

Lines 3 and 6 here look very similar now because of the Java syntactic sugar we've applied. 由于我们应用的Java语法糖,现在第3行和第6行看起来非常相似。 But line 3 is really like our SpecialList() example - the reference type through which add() is invoked is an anonymous subclass of ArrayList<A> . 但第3行与我们的SpecialList()示例非常相似 - 调用add()的引用类型是ArrayList<A>的匿名子类。 Line 6, though, is pretty much what it appears to be - and so it fails for the same reason it did in the first couple examples. 然而,第6行几乎就是它的样子 - 因此它失败的原因与它在前几个例子中所做的相同。

Similar analysis will explain the other weird distinctions you're seeing. 类似的分析将解释你所看到的其他奇怪的区别。

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

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