• No results found

GENERICS AND SUB-TYPE

In document Advanced Java Programming by Uttam Roy (Page 140-144)

G eneric P roGramminG

6.9 GENERICS AND SUB-TYPE

The sub-type and super-type relationships with respect to generics appear to be confusing to beginners. However, understanding them is important and clearly makes you better equipped to work with generics.

In Java, we know that a reference of type T may refer to an object of type S if S is either T or S is a sub-type of T. For example, an Object type reference can hold an Object or any of its sub-type object such as Integer.

Object oRef = new Object(); //oRef refers to an Object String sRef = new String("Java"); //sRef refers to a String oRef = sRef; //oRef now refers to a String Similarly, the following is also valid as Integer and Double are sub-type of Number: Number nRef = new Integer(2); //nRef refers to an Integer nRef = new Double(3.2); //nRef now refers to a Double The same is also true for generics.

List<Number> ln = new ArrayList<>(); ln.add(new Integer(2));

ln.add(new Double(3.2));

Since, Integer and Double are sub-type of Number, a list of Number may hold Integer as well as Double objects. However, List<Integer> is not a subtype of List<Number>. Consider the following example:

List<Integer> li = new ArrayList<Integer>(); ln = li;

Here we have created a List of Integer and assigned it to a List of Number. It seems to be obvious as – “a List of Integer is indeed a List of Number”. But this is not correct in the generics world! [Figure 6.1: ]

(i) (ii) (iii) Number Integer List<Number> List<Integer> X List<Number> List<Integer> Object

Figure 6.1: Generics and sub-typing (i) Integer is a sub-type of Number (ii) However, List<Integer> is not a sub-type of List<Number> (iii) Their common parent is Object

This piece of code does not compile because if it could have compiled we could add a Double object as follows:

ln.add(new Double(3.2));

This could have resulted in ClassCastException at runtime and type safety could not be achieved. In general, given two concrete types A and B, AClass<A> has no relationship to AClass<B>, regardless of whether or not A and B are related. The common parent of AClass<A> and AClass<B>

is Object. To illustrate this, a beautiful example was given in Java’s tutorial. We are also using that example to demonstrate this.

Consider the following type hierarchy: class Animal {/*...*/}

class Lion extends Animal {/*...*/} class Butterfly extends Animal {/*...*/}

Since, Lion and Butterfly are kind (sub-type) of Animal, either may be supplied when an Animal is required:

Animal a = new Lion(); //a refers to a lion

a = new Butterfly(); //a now refers to a butterfly

We know that lions are kept in lion cages (with bars strong enough to prevent the lions from going out):

List<Lion> lionCage = new ArrayList<Lion>(); lionCage.add(new Lion());

Similarly, butterflies are kept in cages whose bars need not be so strong but should be spaced closely enough to hold in the butterflies:

List<Butterfly> butterflyCage = new ArrayList<Butterfly>(); butterflyCage.add(new Butterfly());

Now, think about a cage of an animal.

List<Animal> animalCage = new ArrayList<Animal>();

Since, it is an animal cage, it should ideally be capable of keeping all animals including a lion and a butterfly. So, its bars should not only be strong, but also spaced closely. If such a cage really exists, it is possible to keep a lion as well as a butterfly together there.

animalCage.add(new Lion()); animalCage.add(new Butterfly());

Now, let us think about this question: “Is a lion cage a kind of animal cage? i.e. is List<Lion> a kind of List<Animal> or is List<Butterfly> a kind of List<Animal>”. If we think critically, we shall find the answer as “no” in both cases. This is because ideally, if a lion cage could have been a kind of animal cage, we could keep butterflies there since a butterfly is also a kind of animal. However, bars of a lion cage are not close enough to prevent butterflies from escape. Therefore, the following assignment is wrong:

animalCage = lionCage; //compile time error

Similarly, a butterfly cage is not a kind of general animal cage as it is too weak to keep lions. Therefore, the following assignment is also wrong:

animalCage = butterflyCage; //compile time error Java generics tutorial developer ironically added the following:

Before generics, the animals could be kept in improper kinds of cages. As a result, it would be possible for them to escape.

6.10 WILDCARDS

So, how do you specify a “List of some kind of animal” syntactically? This is represented as: List<? extends Animal> someAnimalCage;

and is read as “List of unknown type which is either Animal or a sub-type of it”. So, for this type of cage, either a lion cage or a butterfly cage may be provided [Figure 6.2: ].

someAnimalCage = lionCage; someAnimalCage = butterflyCage;

GENERIC PROGRAMMING 123 (i) (ii) List<Animal> List<Lion> List<Butterfly> X List<Lion> List<Butterfly> List<? extends Animal> X

Figure 6.2: Wildcards (i) Without wildcard (ii) With wildcard

In generics, an unknown type is represented by the wildcard character “?”. It may be used as the type of a parameter, field, or local variable. Although, it may be used as a return type, being more specific is better. However, the wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a super type.

6.10.1 Upper-bound wildcard

Since, <? extends Animal> represents any type bounded by the type Animal, it is said to be

bounded wildcard with the upper bound Animal.

Note that, the keyword extends is used in a general sense to mean either extends for classes or implements for interfaces.

However, we cannot add any lions or butterflies to this kind of cage. This is because, if someAnimalCage refers to a lion cage (bars are not so close) and if we put some butterflies there, they will fly away. Similarly, if someAnimalCage refers to a butterfly cage (bars are not so strong) and if we put some lions there, they will break the cage and escape. So the following are incorrect:

someAnimalCage.add(new Lion()); someAnimalCage.add(new Butterfly());

What will the usage of this kind of cage be then? Don’t worry. Although, we cannot put anything (except null) there, we can still view its contents.

for(Animal an: someAnimalCage)

an.free(); //assume that the Animal class has a method free()

Since, someAnimalCage contains Animal or any sub-type of Animal, it is safe to assign its elements to an Animal type variable.

A method may also use such upper-bounded wildcards. static void f(List<? extends Animal> l) {/*...*/}

With this declaration, the method f() may be invoked supplying both lionCage as well as butterflyCage:

f(lionCage); //OK f(butterflyCage); //OK

The method f() can access the list elements as type Animal: static void f(List<? extends Animal> l) {

for (Animal elem : l) { // ...

} }

Similarly, if we want to write the method that works on List<Number>, List<Integer>, List<Double>, List<Float> etc., we specify it as:

static void g(List<? extends Number> l) {/*...*/}

In general, for an “in” parameter that serves up data to the function, an upper-bounded wildcard is used.

6.10.2 Lower-bound wildcard

Like an upper bounded wildcard that restricts the unknown type to be a specific type or a subtype of that type, a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type. A lower bounded wildcard is written in the same way except the keyword super is used instead of extends.

List<? super Integer> list;

This represents a ‘List of unknown type that is either Integer or any super type of it’. The list type that this list can hold is Integer types. Consider the following lists:

List<Number> ln = new ArrayList<Number>(); List<Object> lo = new ArrayList<Object>(); List<Integer> li = new ArrayList<Integer>(); With this, the following are correct:

list = li; list = ln; list = lo;

Since, the list can hold an Integer type or any of it’s super type object, it is always safe to add Integer objects to this list.

list.add(new Integer(2));

However, since we do know what type of list it actually is, list elements can be type cast to Object only:

for(Object o : list) System.out.println(o);

It is not possible to specify both upper bound and lower bound for a wildcard simultaneously.

6.10.3 Unbounded wildcard

Java generics also provides another kind of wildcard called unbounded wildcard and is represented as ‘?’. For example, List<?> represents a list of unknown types and can hold arbitrary types of lists. To appreciate its concept, let us try to write a method that prints the list of any type:

It could look like this:

static void printList(List<Object> list) { for (Object o : list)

System.out.print(o + " "); }

Note that even if String, Integer and Double etc. are sub-types of Object, List<String>, List<Integer>, List<Double> etc. are not sub-types of List<Object>. So, we can only pass List<Object> to this method. This means, this method can only print list of Object and no other.

To write a generic printList() method, we can use List<?>: static void printList(List<?> list) {

for (Object o : list)

System.out.print(o + " "); }

We can now pass different kinds of lists to this method for printing: List<Integer> li = ... List<String> ls = ... List<Double> ld = ... printList(li); printList(ls); printList(ld);

GENERIC PROGRAMMING 125 Like upper-bounded list, adding elements is not allowed.

List<?> list = new ArrayList<String>(); list.add(new Object()); // compile time error

When the actual type parameter is ?, it stands for some unknown type. The add() method takes arguments of type T, the element type of the list. An argument to add() must be a subtype of this unknown type. Since we don’t know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type.

C++ templates do not provide any facility like bounded or unbounded type. This is another point where Java generics differs from C++ templates.

6.10.4 Wildcard and sub-typing

We know that although Integer is a subtype of Number, there is no relationship between List<Integer> and List<Number>. Figure 6.3: shows the relationships between several List classes declared with both upper and lower bounded and unbounded wildcards.

List<? extends Number> List<? super Integer> List<?>

List<? extends Integer> List<Integer>

List<? super Number> List<Number>

Figure 6.3: A hierarchy of several generic List class declarations

In document Advanced Java Programming by Uttam Roy (Page 140-144)