Search code examples
javagenericsexceptionsuperocpjp

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!


Solution

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