In Kathy Sierra’s book on SCJP we learn that, When we write
List <A> list = new ArrayList <A> ();
This means that this list will accept elements of type A only, not it’s subtype or supertype, only type A. Then I came across this example
import java.util.*;
interface A {
public void a();
}
class B implements A {
public void a() { }
public void b() { }
}
class C extends B {
public void a() { }
}
class D extends C {
}
public class Generics {
public static void main() {
List <B> lst = new ArrayList <B> ();
lst.add(new B());
lst.add(new C());
lst.add(new D());
}
}
Here a list has been declared with B as it’s bound type. But it looks like it’s accepting objects of it’s subtype also. Why is it so? And if it is possible then is this declaration available in java
List <? extends B> list = new ArrayList <B> ();
Now with this notation we tell compiler that any type which extends B is allowed to enter this list. Please help, I am really confused
The problem in teaching generics with the List examples is that one can understand the examples and still not the rule.
With the advent of generics, the compiler hasn’t been instructed with human knowlege, so it doesn’t know that a
List<A>is an ordered sequence of elements (it could be a proboscidean with long, curved tusks), and thus it can’t guarantee that a List will accept elements of typeAonly. All the compiler knows is supplied by the following definitionThis code does not define a single type: it defines an infinite series of types:
depending on how you instantiate the parameterized type. Since in Java each subtype is a valid replacement for its supertypes, a
Rectangleis a legal argument to aList<Shape>‘sadd(Shape s).Here is when one thinks “Generics, I finally got you!“. Then he fires an IDE, types
and the compiler refuses to compile. “- What’s going on here? I can add a rect to a list of shapes, but a list of rects is not a list of shapes?”. The problem is one thinks in terms of lists, shapes and rectangles, but the compiler doesn’t know any of these. It sees
so it wonders “- Is
Sa valid replacement for typeT?”. Phrased differently, isTa parent ofSin the type hierarchy, as constructed according to the Java rules? So it carefully checks its old Java book and finds that no,X<A>is not aX<B>parent. Illegal code. Stop.“- But a list of rectangles definitely contains shapes! I just compiled a program which added a bunch of rectangles to a
List<Shape>!”. This doesn’t mean anything to a compiler, it’s not such a smart program – you see… It can only interpret those few rules it learned some years ago and can’t even tell the difference between aListand aMammoth. It only knows its old Java book, and in the book is clearly stated and remarked that there is no subtyping relation betweenX<A>andX<B>, no matter howAandBrelate to each other. “- If only generics were implemented like arrays… You, stupid Java people…” In fact:Maybe you now got the point. After all, those Java people are not so stupid… At least, those fancy rules they chosen for their type system serve a purpose, at least they did it for a practical reason. They made arrays covariant, but generics invariant. Since both arrays and lists’ contents can be changed (they are mutable), this exposes arrays (but not lists) to the problem seen above. For example, Scala achieves type safety by making lists covariant but immutable (a
List[Integer]can be assigned to aList[Number], but they’re read-only) and arrays mutable but invariant (you can’t use anArray[Integer]where anArray[Number]is required, but you can modify their contents).Finally, to port the subtyping relation between
AandBinto the generics world, one can use the wildcard?, but that’s matter for a whole new story. I just anticipate that in the old Java book is written thatList<Rectangle>does not extendsList<Shape>, but it’s a legitimate child ofList<? extends Shape>.