Shades88
Shades88

Reputation: 8360

Java generics and inheritence confusion

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

Upvotes: 0

Views: 197

Answers (4)

Raffaele
Raffaele

Reputation: 20885

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 type A only. All the compiler knows is supplied by the following definition

interface List<E> {
  public void add(E);
  public E get(int index);
}

This code does not define a single type: it defines an infinite series of types:

interface ListOfStrings {
  public void add(String s);
  public String get(int index);
}

interface ListOfShapes {
  public void add(Shape e);
  public Shape get(int index);
}

depending on how you instantiate the parameterized type. Since in Java each subtype is a valid replacement for its supertypes, a Rectangle is a legal argument to a List<Shape>'s add(Shape s).

Here is when one thinks "Generics, I finally got you!". Then he fires an IDE, types

List<Shape> shapes = new LinkedList<Rectangle>();

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

T reference = <expression returning type S>

so it wonders "- Is S a valid replacement for type T?". Phrased differently, is T a parent of S in 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 a X<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 a List and a Mammoth. It only knows its old Java book, and in the book is clearly stated and remarked that there is no subtyping relation between X<A> and X<B>, no matter how A and B relate to each other. "- If only generics were implemented like arrays... You, stupid Java people..." In fact:

String[] strings = new String[10];
Object[] objects = strings;
objects[0] = new Rectangle(); // ArrayStoreException at runtime

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 a List[Number], but they're read-only) and arrays mutable but invariant (you can't use an Array[Integer] where an Array[Number] is required, but you can modify their contents).

Finally, to port the subtyping relation between A and B into 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 that List<Rectangle> does not extends List<Shape>, but it's a legitimate child of List<? extends Shape>.

Upvotes: 1

Brian Agnew
Brian Agnew

Reputation: 272407

There's a distinct between the types permitted in a collection, and the declared type of the collection itself. For

List <A> list = new ArrayList <A> ();

it's declaring a list of elements. Each element is-a A. Each element can be a subclass of A, but the list itself is viewed as a list of As.

Note the distinction. A banana is-a fruit. But a collection of bananas is not a collection of fruit (counterintuitively). Otherwise you could take that collection of bananas, view it as a collection of fruit, and add an apple.

List <? extends B> list = new ArrayList <B> ();

means that the list can be a list of B, or a list of subtype of B. e.g. the below are all valid:

List <? extends B> list = new ArrayList <B> ();
List <? extends B> list = new ArrayList <C> ();
List <? extends B> list = new ArrayList <D> ();

Upvotes: 2

Ashish
Ashish

Reputation: 520

"This means that this list will accept elements of type A only, not it's subtype or supertype, only type A" - This is incorrect. I can rewrite your code as follows:

List <A> list = new ArrayList <A> ();
B b = new B();
B c = new C();
B d = new D();
list.add(b);
list.add(c);
list.add(d);

variable b, c and d are all of type 'B' when we are assigning them to instance of B, C and D. Polymorphism?

If you see, B, C and D implements A and hence I can assign instances of B, C, D to A.

Upvotes: 0

paulm
paulm

Reputation: 5892

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

This is wrong - it will accept any derived types. Also Java generics is implemented in the compiler using erasure.

It is the same as having a method parameter that takes B, it should accept B, C and D.

Also note that at runtime because of erasure there is no type checking so:

List <B> lst = new ArrayList <B> ();

Becomes

List <object> lst = new ArrayList <object> ();

This might help you:

http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html

Upvotes: 0

Related Questions