badCoder badCoder - 1 month ago 4
Java Question

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.

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:

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


Thanks for your answers!

Answer

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>.

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. The expression itself, i.e. 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.

The second issue (with list2) has the same cause.

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>. This means the generic parameter can be B or its superclass, A. What if it's a List<B>? You can't add an A to a List<B>; therefore the compiler rejects this code.

Comments