I've read some articles about Covariance, Contravariance, and Invariance in Java, but I'm confused about them.
I'm using Java 11, and I have a class hierarchy A => B => C (means that C is a subtype of B and A, and B is a subtype of A) and a class Container:
class Container<T> {
public final T t;
public Container(T t) {
this.t = t;
}
}
for example, if I define a function:
public Container<B> method(Container<B> param){
...
}
here is my confusion, why does the third line compile?
method(new Container<>(new A())); // ERROR
method(new Container<>(new B())); // OK
method(new Container<>(new C())); // OK Why ?, I make a correction, this compiles OK
if in Java Generics are invariant.
When I define something like this:
Container<B> conta = new Container<>(new A()); // ERROR, Its OK!
Container<B> contb = new Container<>(new B()); // OK, Its OK!
Container<B> contc = new Container<>(new C()); // Ok, why ? It's not valid, because they are invariant
Covariance is the ability to pass or specify a subtype when a supertype is expetced. If your C class extends B, then C is a child class of B. This relationship between C and B is also called is-a relationship, where an instance of C is also an instance of B. Therefore when your variable contc is expecting a B instance and you're passing new C(), since new C() is an instance of C and C instance is (also)-an instance of B, then the compiler allows the following writing:
Container<B> contc = new Container<>(new C());
Conversely, when you're writing
Container<B> conta = new Container<>(new A());
you're receiving an error because A is a supertype of B, there is no is-a relationship from A to B, but rather from B to A. This is because every instance of B is also an instance of A, but not every instance of A is an instance of B (To make a silly example, every thumb is a finger but not every finger is a thumb). A is a generalization of B; therefore it cannot appear where a B instance is expected.
Here there's a good article expanding the concept of covariance in java.
https://www.baeldung.com/java-covariant-return-type
The question's examples don't demonstrate the invariance of generics.
An example which does demonstrate this would be:
ArrayList<Object> ao = new ArrayList<String>(); // does not compile
(You might incorrectly expect the above to compile, because String is a subclass of Object.)
The question shows us different ways to construct Container<B> objects - some of which compile and others which do not, because of the inheritance hierarchy of A, B and C.
That diamond operator <> means that the created container is of type B in every case.
If you take the following example:
Container<B> contc = new Container<>(new C()); // compiles
And re-write it by populating the diamond with C, the you will see that the following does not compile:
Container<B> contc = new Container<C>(new C()); // does not compile
That will give you the same "incompatible types" compilation error as my ArrayList example.
One of the boons introduced with Java 7 is the so-called diamond operator <>.
And it has been with us for so long, that it's easy to forget that every time when diamond is being used while instantiating a generic class the compiler should infer the generic type from the context.
If we define a variable which will hold a reference to a list of Person objects like this:
List<Person> people = new ArrayList<>(); // effectively - ArrayList<Person>()
the compiler will infer the type of the ArrayList instance from the type of the variable people on the left.
In the Java language specification, the expression new ArrayList<>() is being described as a class instance creation expression and because it doesn't specify the generic type parameter and is used within a context, it should be classified as being a poly expression. A quote from the specification:
A class instance creation expression is a poly expression (§15.2) if
it uses the diamond form for type arguments to the class, and it
appears in an assignment context or an invocation context (§5.2,
§5.3).
I.e. when diamond <> is used with a generic class instantiation, the actual type will depend on the context in which it appears.
The three statements below represent the case of so-called assignment context. And all three instances Container will be inferred as being of type B.
Container<B> conta = new Container<>(new A()); // 1 - ERROR because `B t = new A()` is incorrect
Container<B> contb = new Container<>(new B()); // 2 - fine because `B t = new B()` is correct
Container<B> contc = new Container<>(new C()); // 3 - fine because `B t = new C()` is also correct
Since all instances of container are of type B and of parameter type expected by the contractor also will be B. I.e. can provide an instance of B or any of its subtypes. Therefore, in the case 1 we are getting a compilation error, meanwhile 2 and 3 (B and subtype of B) will compile correctly.
And it in't a violation of invariant behavior. Think about it this way: we can store in a List<Number> instances of Integer, Byte, Double, etc., that would not lead to any problem since they all can represent their super type Number. But the compiler will not allow assigning this list to any list that is not of type List<Number> because otherwise it would be impossible to ensure that this assignment is safe. And that is what the invariance means - we can assign only List<Number> to a variable of type List<Number> (but we are free to store any subtype of Number in it, it's safe).
As an example, let's consider that there's a setter method in the Container class:
public class Container<T> {
public T t;
public Container(T t) {
this.t = t;
}
public void setT(T t) {
this.t = t;
}
}
Now let's use it:
Container<B> contb = new Container<>(null); // to avoid any confusion initialy `t` will be assigned to `null`
contb.setT(new A()); // compilation error - because expected type is `B` or it's subtype
contb.setT(new B()); // fine
contb.setT(new C()); // fine because C is a subtype of B
When we deal with a class instance creation expression using diamond <>, which is passed to a method as an argument, the type will be inferred from the invocation context as the quote from the specification provided above states.
Because method() expects Container<B>, all instances above will be inferred as being of type B.
method(new Container<>(new A())); // Error
method(new Container<>(new B())); // OK - because `B t = new B()` is correct
method(new Container<>(new C())); // OK - because `B t = new C()` is also correct
Note
The important thing to mention that prior to Java 8 (i.e. with Java 7, because we are using diamond) the expression new Container<>(new C()) will be interpreted by the compiler as a standalone expression (i.e. the context will be ignored) creating an instance of Container<C>. It means your initial guess was somewhat correct: with Java 7 the below statement would not compile.
Container<B> contc = new Container<>(new C()); // Container<B> = Container<C> - is an illegal assignment
But Java 8 has introduced a feature called target types and poly expressions (i.e. expressions that appear within a context) that insures that context will always be taken into account by the type inference mechanism.
Related
we can achieve the output in two ways one is typecasting and one is without typecasting
A a=new B() // without typecaste
A a = (A)a// with Typecaste
in both ways we get same output.so, what is the use of typecasting
Let's assume that you have a list of Animals. and you have Tigers and Lions in it.
ArrayList<Animal> animals = new ArrayList<>();
//add some Tigers and some Lions
//sort so Tigers are at the beggining of the list
Tiger t = (Tiger)animals.get(0);
Without casting you will get type missmatch at compile time. With a cast you only risk ClassCastException which can be easy caught with a try-catch
It's just an example of a proper use of class casting in Java.
Casting is for "the opposite direction", i.e. for converting to a expression of a subtype of the original expression.
Example
Given
Object o = "Hello World";
String s = o;
does not compile, but
String s = (String) o;
compiles. This may yield a ClassCastException however, e.g. if a Integer was stored in o.
Casting has different uses. Unfortunately, your example doesn't exercise any useful example of casting since you create an instance of A (a) then cast it to an A.
What you need to understand is there are apparent types and actual types. An apparent type would be List<T> list;. Here we see that it's a list. But the actual type might be an ArrayList<T> (List<T> list = new ArrayList<>();). In this scenario we can, with care, cast the apparent type to the actual type. This would allow us to then use the functionality of the actual type. For example, let's look at some code; given:
List<Integer> list = new ArrayList<>();
ArrayList<Integer> aList;
LinkedList<Integer> lList = new LinkedList<>();
We can do this without issue (although dangerous in general)...
// Dangerous but OK with a cast
// list might not be an ArrayList
aList = (ArrayList<Integer>) list;
// Use ArrayList methods
aList.trimToSize();
list = lList;
LinkedList<Integer> danger = (LinkedList<Integer>) list;
...but it's also possible to do:
aList = (ArrayList<Integer) list;
// Use ArrayList methods
aList.trimToSize();
// list = lList;
LinkedList<Integer> danger = (LinkedList<Integer>) list;
The last snippet results in a ClassCastException because list isn't a LinkedList.
Casting goes beyond that though. Consider when you have two integers you want to divide. Without a cast you could end up with an integer result where a floating point is more appropriate. Consider:
int i = 2;
int j = 3;
System.out.println("No cast: " + i/j + " ;With cast: " + (double)i/j);
Output:
No cast: 0 ;With cast: 0.6666666666666666
So, it depends on the use case.
A a = new B();
will only works if B inherit from A.
If B inherit from A, the type cast is not required as B is a A. Type cast will be necessary if you need to type cast to a subclass:
A a = new B();
B b = (B) a;
While this would be illegal :
A a = new A();
B b = (B) a;
as a is not a B.
Java implicitly upcast with assignment, so in the code you've provided the casting operator is redundant; a is already of type A:
A a = new B(); // without typecast operator (implicit upcast)
A a = (A)a; // with redundant typecast operator
One reason to have a casting operator is that you may also wish to downcast (which is not done implicitly in Java). For instance, when a is a type A reference to an object of class B (e.g. when B is a subclass of A) one may need to downcast to access certain methods:
A a = new B(); // implicit upcast
C c = ((B)a).methodOfBOnly(); // explicit downcast
You may also want to check this question on why Java doesn't do implicit downcasting.
There can be times when upcasting needs to be done explicitly as well. For instance, if a class contains overloaded methods
C method(A x){/*does one thing*/}
C method(B x){/*does another*/}
and assuming b is of type B, the calls to method((A)b) and method(b) would behave differently.
A a=new B()
is applicable only when class B extends class A. In this way the extra methods that are available in class B other than class A will be available with reference a.
When you do this
A a = (A)a
Then actually you are down casting the object of class B into an object of class A. And it is true that child can be type cast to parent. After this statement the reference a will not be able to call any method of class B which were not in class A because now the reference a points to an object of class A.
It is useful in many scenarios.
For example, you want to have a collection of Objects that point to same base class. Instead of maintaining separate collections for each sub class, you maintain a single collection of base class. And then when you want to use any child object you type cast the base class object to child class object to do that.
ArrayList<Base> children = new ArrayList<Base>();
children.add(new Child1());
children.add(new Child2());
Console.WriteLine(((Child1)children.get(0)).getChildName());
Console.WriteLine(((Child2)children.get(1)).getChildName());
Now base class does not have any method named getChild1Name or getChild2Name. And you need to typecast object of base class to respective child class to do that.
Consider this piece of code:
public class Main {
public static void main(String[] args) {
Cat<Integer> cat = new Cat();
Integer i= cat.meow();
cat.var = 6;
}
}
public class Cat<E> {
public E var;
public E meow() {
return null;
}
}
As per my understanding since I've not specified the type parameter on LHS, it would be taken as Object. And Cat should become Cat<Object> because for the variable declaration to make any sense T must translate to a Class/Interface reference. Is this a correct understanding of how it works? How is type parameter T handled in case of raw types?
I've discussed this on chat and got following explanation which is way over my head:
Generics works because types are erased. Consider T an erasure of
#0-capture-of-Object. When T isn't specified (rawtyped), it is #0-capture-of-(nothing)
What does #0-capture-of-(nothing) mean?
Side note: since generic types are implemented as compile time transformations it would be easier to understand them if one could see the final translated code. Does anyone know a way to see the translated code (not byte code) for a class?
No,
raw types are not as if they are paramterized with Object, nor are they like wildcard types (<?>).
For raw types, generics are turned off.
This code is compiles (with warnings):
Cat c1 = new Cat<String>();
Cat<Integer> c2 = c1;
This code does not:
Cat<? extends Object> c1 = new Cat<String>(); // btw: this is the same as Cat<?>
Cat<Integer> c2 = c1; // error
neither does this:
Cat<Object> c1 = new Cat();
Cat<Integer> c2 = c1; // error
As for the type of var:
the type of the field after compilation is whatever the upper-bound of the parameter is (Object if none is specified). But what does the compiler do if we access var?
Cat<String> c1 = ...
String c1Var = c1.var;
This code compiles without error, but what the compiler will actually compile is this:
Cat c1 = ...
String c1Var = (String) c1.var;
As you can see, var is always treated as a field of type Object, but with generics, the compiler automatically inserts type-safe casts. That's all. If you use raw types, you have to do it yourself. Either way, when you put a Cat that stores an integer in a Cat<String> variable, you will only get a runtime exception if you try to read var.
A quiz
Now look at the declaration of Collections.max. Why do you think the parameter is defined as T extends Object & Comparable<? super T>?
Answer encoded in rot13:
Fb gung nsgre pbzcvyngvba gur erghea glcr vf Bowrpg, abg Pbzcnenoyr. Guvf vf arrqrq sbe onpxjneqf pbzcngvovyvgl (gur zrgubq vgfrys vf byqre guna trarevpf).
Edit:
Here is another good example that I just stumbled upon:
class Foo<T> {
public <V> V bar(V v) { return v; }
}
//compiles
Foo<Object> foo = new Foo<Object>();
Integer i = foo.bar(1);
//compiles
Foo<?> foo = new Foo<String>();
Integer i = foo.bar(1);
// fails
Foo foo = new Foo();
Integer i = foo.bar(1); // error: Object cannot be converted to Integer
Using no parameters disables generics entirely.
This code is valid:
Cat c = new Cat<Integer>();
c is now of the Raw Type Cat.
This is not valid:
Cat<Object> c = new Cat<Integer>(); // Compiler error
So, it's not exactly the same. Though you can, after the first line, do things like:
c.var = 5;
System.out.println(c.var);
c.var = 1;
System.out.println(c.var);
c.var = "test";
System.out.println(c.var);
Outputs:
5
1
test
#Cephalopod has provided the correct answer, however I'd like to expand on that with some of my own explanation.
for the variable declaration to make any sense T must translate to a Class/Interface reference.
That is correct. Generics are a compile time transformation. Runtime system has no notion of abstract types. So before the class is loaded into memory the abstract type T must be replaced by an actual type reference.
Run the following code:
System.out.println(Cat.class.getMethod("meow").getReturnType());
System.out.println(Cat.class.getField("var").getType());
The output is:
class java.lang.Object
class java.lang.Object
The formal type parameter E has been replaced with Object.
Cat should become Cat<Object>
Wrong. Cat will stay Cat. Why? Look at the decompiled class file for Main:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Integer i = (Integer)cat.meow();
cat.var = Integer.valueOf(6);
}
}
The purpose of specifying formal type parameter with <> is to enable compiler to generate explicit casts.
When you say new Cat() it doesn't have to turn into anything, the compiler simply won't generate a cast and the method call would look like:
Integer i = cat.meow(); // No cast at all
Are generic type parameters converted to Object for raw types?
To clarify what is being asked here, the above questions means: Is E replaced with java.lang.Object if I don't specify anything when instantiating Cat.
Actually E would be replaced with java.lang.Object even if you specified <Integer> when instantiating Cat. The replacement/transformation is done at compile time while the instantiation is at runtime. How you use the type isn't going to change its class definition.
Generic types defined in objects like the
Cat c = new Cat<Integer>();
are only intended to provide the compiler with the chance to check that the types will match at runtime.
Generic types assigned in class definitions are retained in the compiled class.
public class Cat<T extends Number>{}
Or
public class Intcat extends Cat<Integer>{}
The runtime knows that the generic argument is bound by Number in the first case and is Integer in the first case.
I have no links to back this up, but I'd rather assume that c becomes raw type Cat, not Cat<Object>.
Raw types don't handle parameter T, which is why they are prone to errors.
javadoc says: A raw type is the name of a generic class or interface without any type arguments.
That chat log seems to mean exactly that, but in a confusing manner.
I actually Do not know How it is actually implemented in the bytecode But from my understanding Cat c = new Cat<Integer>(); Stores the new instance of Cat created by new Cat<Integer>() in the variable c. Now if you query c to know what is the type of var it will reply Integer and not Object because the instance that was created has a type of Integer.
Now If you execute c.var = "Text"; and query c to know what is the type of var. It would reply String. This does not means that by default it is converting <T> to Object. It means that c does not know what is the type of var.
I feel that is why the <?> wild card is used. Cat<?> c = new Cat<Integer>(); it would always convert <T> to Object. That is the reason why it is always advised not the use raw types for generics.
I think Cat c is a RAW type and could be considered as a "wildcard type" like Cat<?>. Since Cat<?> is the supertype of each type of Cat including Cat<Integer>, Cat c may take a new Cat<Integer> object.
This is also mentioned here: Interoperating with Legacy Code
"Most people's first instinct is that Collection really means Collection. However, as we saw earlier, it isn't safe to pass a Collection in a place where a Collection is required. It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection."
...
"So raw types are very much like wildcard types, but they are not typechecked as stringently. This is a deliberate design decision, to allow generics to interoperate with pre-existing legacy code."
I have the following codes:
1. public class Tester
2. {
3. public static void main(String[] args)
4. {
5. A a = new B();
6. System.out.println(a.getClass()); //Prints class B
7. System.out.println(a instanceof A); //Prints true
8. System.out.println(a instanceof B); //Prints true
9. System.out.println(a.valA); //Prints 1
10. System.out.println(a.valB); //Compilation error
11.
12. }
13. }
14.
15. class A
16. {
17. int valA=1;
18. }
19.
20. class B extends A
21. {
22. int valB=2;
23. }
At line 6 it shows that a is of type class B. However when it reaches line 10, the compiler produces an error: location: variable a of type A.
So my question is: What exactly is the class type of a now? Why getClass() shows that it is of type class B, yet the compiler complains it as type A during compilation?
Further more, since a instanceof B is true, why can't I access valB?
To make things clearer:
EDIT: I ran this statement: System.out.println(a); and the output was B#36d98810 which somehow proves that the toString() method of class B was executed. Since variable a can access the toString() method within class B, why can't it access valB which also resides in class B?
Professor Jonathan Shewchuk from UC Berkley explains about shadowing over here. Start at 18 minutes. (If the link changes just google search for CS 61B Lecture 15: More Java)
To answer your question in short there are two types for a variable, static type and dynamic type.
Static type is its Type at compile time
Dynamic type is its Type at run time.
In your example
A a = new B();
The static type of a is A and the dynamic type of a is B.
In Java a variable gets its non static methods from dynamic type
(if the method exists in both the parent and child class)
and
its fields and static methods from the static type.
This is true in C# only if the method is overridden in the sub class
Update:
The line
a instanceof A
tells you whether the dynamic type of a is of type A OR a subclass of A
Update 2:
AN example that illustrates this
public class PlayGround {
public static void main(String[] args) {
Animal a = new Dog();
System.out.print(a.name);// displays animal
System.out.print("\r\n");
a.MakeStaticSound();// displays static animal sound
System.out.print("\r\n");
a.MakeSound();// displays bow wow
}
}
class Animal {
public String name = "animal";
public void MakeSound() {
System.out.print("animal sound");
}
public static void MakeStaticSound() {
System.out.print("static animal sound");
}
}
class Dog extends Animal {
public String name = "dog";
public void MakeSound() {
System.out.print("bow wow");
}
public static void MakeStaticSound() {
System.out.print("static bow wow");
}
}
Please note that the more readable and preferred way to call a.MakeStaticSound() is Animal.MakeStaticSound()
a is not an object. It's a variable.
The type of the variable is A. The type of the object that the value of the variable refers to at execution time is B.
The compiler resolves everything against the compile-time type of the expressions involved - the variable in this case. When trying to resolve the name valB within the compile-time type of A, it fails to find anything - hence the error.
You need to keep in mind that compilation and execution are two different processes that happen at different times and have different kinds of information available to them. The compiler has to predict the future -- it has to decide whether it can guarantee that your code will make sense in the future, at runtime. It does this by analyzing the types of the objects in your code. The runtime, on the other hand, just has to inspect the current state of things.
When you read the line A a = new B(), you are inferring more information about the a local variable than the compiler is. The compiler basically just sees this as A a = <some expression>. It does not take note of the contents of the expression that's used to produce the value for a.
The fact that you've said A a = ... is you telling the compiler: "hey, this a thing I'm going to deal with in the rest of my program, it's just an A, don't assume anything more about it." If you had instead said B a = ..., then you're telling the compiler that it's a B (and the compiler also sees B extends A elsewhere in your code, so it knows it's also an A).
The subsequent expressions a instanceof A, a instanceof B, a.getClass(), and a.toString() are legal, from the compiler's point of view, regardless of the type of a: the instanceof operator and the getClass() and toString() methods are defined for all Objects. (The compiler does not need to predict what value those expressions will produce at runtime, just that they will produce either true or false, some Class<?>, and some String, respectively.)
But then when you come to a.valA and a.valB, the compiler actually has to do some real work. It needs to prove or guarantee that the a object will have a valA and a valB field at runtime. But since you've explicitly told it earlier to just assume that a is an A, it can not prove that it will have a valB field at runtime.
Now, later on, at execution time, the JVM has more information. When it evaluates a.getClass(), it actually looks up the concrete class that's "under the hood" of a and returns it. Similarly for instanceof B -- it looks up the concrete class and thus the result of that expression is true.
a.toString() works similarly. At runtime, the JVM knows that the thing referenced by a is actually a B, so it executes B's toString method.
This is a fundamental property of class inheritance, interfaces, etc.
Class "A" does not have a variable "valB".
If you want to use the variable "valB" in class "B" either, you should first cast Class "A" to "B"
Try :
System.out.println(((B)a).valB);
You should know the difference between object type and instance type. First is determined at compile type and at runtime it's doing the best to keep that type safe. Instance type is a class which object is instantiated.
A a; //this is an object type
new B(); //this is an instance type
A a = new B(); //all together, but a is of type A, having instance of type B.
I have the following code:
public <T extends SomeObject> long doSomething(T someObject){
List<? extends SomeObject> l = new LinkedList<>();
l.add(someObject);
}
this causes a compilation error - telling me that there is no suitable methods found: add(T),
why is that?
If l accept things that extends SomeObject shouldn't it accept someObject as it bounds to extend SomeObject?
List<? extends SomeObject> l
What do you mean by that? Of course it will generate an error.
Take this example :SomeObject is Fruit, you have 2 derived classes Apple and Orange
Your list what will it contain? Apples or Oranges? The compiler cannot tell. So it generates error.
If you replace List<? extends SomeObject> l with List<SomeObject> l. Then this will work because Apple and Orange are both Fruit.
I would advise you to use this statement:
List<T> l = new LinkedList<T>();
This is no less type-safe then
List<SomeObject> l = new LinkedList<SomeObject>();
and additionally gives you an opportunity to get objects of type T from the list without casting. T is already SomeObject so no casting required to call methods of SomeObject on T.
And all that with less typing!
Back to the problem.
First thing to note is that wildcard type "?" means unknown, this is important.
You may, however, specify an upper (? extends) or a lower (? super) constraint to it.
You declared a list as "List".
List is known to have objects of SomeObject inside. but! the exact type of objects is unknown.
Compiler can not say if there are instances of "class A extends SomeObject" or instances of "class B extends SomeObject" inside the list.
If you call list.get() it can only say that there will be an object of type SomeObject.
SomeObject obj = list.get(1); // Ok
But inserting an object of any(!) type is unsafe because the actual type of elements in the list is unknown.
You could wonder why wildcard type ever exists.
It is here to lower restriction in type casting that will be too strict otherwise.
Sample
class A { }
class A2 extends A { }
class B <T> {
void change(T a) { .. };
T read() { .. };
}
If there were no wildcards we would not be able to do this: B<A> b = new B<A2>(); - it does not work.
This is because type conversion from B<A> to B<A2> is unsafe.
Why? Let's look (copied from http://en.wikipedia.org/wiki/Generics_in_Java)
List<Integer> ints = new ArrayList<Integer>();
ints.add(2);
List<Number> nums = ints; // valid if List<Integer> were a subtype of List<Number>
nums.add(3.14);
Integer x = ints.get(1); // now 3.14 is assigned to an Integer variable!
What is the solution? Sometimes, we want to do such assignments or pass parameters in a general way!
Wildcard type helps here: B<? extends A> b = new B<A2>();
Method B.void change(T a) is now disabled - this is what your question was about and explained in the first part.
Method B.T read() is still valid and returns A: A a = b.read();. Yes, it returns A2 actually but to the caller of b.read() it's visible as A.
Wildcard types are widely used in Collections Framework.
I've a collection type:
Collection<A> collecA
And I've a list in my object:
List<B> listB
Where B is extending A
class B extends A { ... }
But I can't do the following:
collecA = listB
I can't understand why since Collection is implemented by List.
Let's assume for a moment you could do what you describe:
class B extends A { ... }
Collection<A> collecA;
List<B> listB;
collecA = listB; // normally an error, but lets pretend its allowed
collecA.add(new A()); // PROBLEM!
The method call collecA.add(new A()) appears okay since collecA is a collection
that holds As. However, if the above assignment were allowed, then we have a
problem becausecollecA is really reference to a List<B> instance - I just
added an A into a list that can only hold Bs!
Asker also said:
I can't understand why since Collection is implemented by List.
It doesn't matter that Collection is a superclass of List. This assignment is illegal even if you used two lists.
class B extends A { ... }
List<A> listA;
List<B> listB;
listA = listB; // still an error, still leads to the same problem
The key is that the List<A> variable can reference only Lists that can hold As. However, a List<B> instance cannot hold As. Therefore, a List<A> variable like listA cannot be assigned a reference to a List<B> instance referred to bylistB.
Or more generally speaking: B being a subclass of A does not imply that SomeGenericClass<B> is a subclass of SomeGenericClass<A> (JLS §4.10: Subtyping does not extend through generic types: T <: U does not imply that C<T> <: C<U>.)
It was this example/analogy from the Java Generics Tutorial that helped me understand this:
http://java.sun.com/docs/books/tutorial/java/generics/subtyping.html
"Understanding why becomes much easier if you think of tangible objects — things you can actually picture — such as a cage:
// A cage is a collection of things, with bars to keep them in.
interface Cage<E> extends Collection<E>;
...
Cage<Lion> lionCage = ...;
Cage<Butterfly> butterflyCage = ...;
But what about an "animal cage"? English is ambiguous, so to be precise let's assume we're talking about an "all-animal cage":
Cage<Animal> animalCage = ...;
This is a cage designed to hold all kinds of animals, mixed together. It must have bars strong enough to hold in the lions, and spaced closely enough to hold in the butterflies.
...
Since a lion is a kind of animal (Lion is a subtype of Animal), the question then becomes, "Is a lion cage a kind of animal cage? Is Cage<Lion> a subtype of Cage<Animal>?". By the above definition of animal cage, the answer must be "no". This is surprising! But it makes perfect sense when you think about it: A lion cage cannot be assumed to keep in butterflies, and a butterfly cage cannot be assumed to hold in lions. Therefore, neither cage can be considered an "all-animal" cage:
animalCage = lionCage; // compile-time error
animalCage = butterflyCage; // compile-time error
"
Collection<? extends A> collecA
This fixes it. The problem is not the List extends Collection, but the generic types instead.
Java generics are not covariant.
See Java Theory and Practice: Generics Gotchas for further details.
The page shows a simple example that would havoc the type-system if it was covariant:
Imagine you could assign a List<Integer> to a List<Number>. Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415)); // ERROR: Adds a float to list, which is a list of Integers!
You can assign List<B> to Collection<B>, but not List<B> to Collection<A>.
Imagine what would happen if this were possible:
List<B> = new ArrayList<B>();
Collection<A> collecA = listB; //Assume that this line compiles
collecA.add(new A());
B item = listB.get(0); //ClassCastException!
As you see, we "fooled" the generics type system, by adding an instance of concrete type A to a collection that was supposed to have only objects of type B (or descendants).
As a consequence, the last line which performs an implicit cast to B fails with a ClassCastException. What's wrong with it? The compiler can't guarantee type safety, and that's against one of the Java generics principles.
Therefore, it has been decided that List<B> is Collection<B>, but NOT List<A> (or Collection<A>).
As a side comment, it is interesting to note that arrays don't follow the same rules: String[] is an Object[], and assignments are legal.