Java Generics - 2. Bounding
This is part 2 of the 9 part series on Java Generics
Prev Topic
|
Topics
1. Introduction
2. Bounding
8. Target Types
9. Type Erasure
|
|
Java Generics - Bounding
Let us define an example hierarchy of classes in order to
understand the java generics better. We have a class called Fruit,
which has two sub classes Grape and Mango. Each of these has
two sub classes. There are two types of Grape – Chardonnay and Shiraz
(these classes extend the class Grape). Similarly there are two types of Mango
– Alphonso
and Hayden.
In our fruit hierarchy, there is also an interface called Exportable, which has
nothing to do with the fruit, but defines if these fruits can be exported and
what is their export price. Only the grape, Shiraz and the mango Alphonso
can be exported. With this in mind let us now explore the bounding with the
java generics.
Let us also define another set of classes for grains too.
These have a hierarchy similar to the Fruit hierarchy. The only common
feature is that some of the grains are also Exportable.
In the examples above for the class Bean<E>, we found
that the type was declared as Bean<E> or in case of Pair it
was declared as Pair<L, R>. These are the example of unbounded types.
What it means is that any class can be used as the type. The compiler actually converts these types (E, L,
R, etc) to Objects. We will
discuss the internal details when we are in the section for Type Erasure.
Let us say you want to write a generic class which takes
only the Number types (or the classes which are extended from the class java.lang.Number).
Let’s call that class NBean. The declaration of a generic
class is simple; just replace ‘E’ by ‘E extends Number’.
public class NBean<E extends Number> {
private E e;
public void set(E e){
this.e = e;
}
public E get(){
return e;
}
}
|
This is also known as upper bounding. The type is bounded by
the abstract class java.lang.Number. This class can only be used with the type Number
or its subclasses such as Integer, Double, BigInteger
etc.
NBean<Integer>
bi = new NBean<>();
bi.set(new Integer(7));
NBean<Double>
di = new NBean<>();
di.set(new Double(8.45d));
NBean<BigInteger>
bbi = new NBean<>();
bbi.set(new BigInteger("1000"));
double sum = bi.get().doubleValue() + di.get().doubleValue()
+ bbi.get().doubleValue();
System.out.println("Sum = " + sum);
NBean<String>
bs = new NBean<String>(); // compiler error
|
If you try using the class with the String class for
instance, you would get the compiler error - Bound mismatch: The type String
is not a valid substitute for the bounded parameter <E extends Number> of
the type NBean<E>. The last line below would give a compiler
error as String type does not extend Number. Can we bound the
type with an interface instead of a class? Yes of course, and the syntax
remains the same. In out example, if we want to create a class called ExportableBean
which can be used only with the types that are Exportable, then simply
bound the type as “E extends Exportable”. Everything else looks like the body of
the class NBean.
Can we extend the typed class? Yes surely we can. If the new
class that you are declaring is generic and is also extending a generic class then
you should make sure that the generic parameter is passed along the super class
too (in this case the Bean<E> class). Thus your declaration would look
like the one below
public class MyBean<E> extends Bean<E> { }
|
In fact it is actually no different from the declaration below.
public class MyBean<T> extends Bean<T> { }
|
What if you want your class MyBean to only be applicable to
the class Fruit? The declaration would be the one shown as below. What
you are implying is that E actually must extend Fruit.
It does not make sense to imply that again when writing the ‘extends
Bean<E>’ clause.
public class MyBean<E extends Fruit> extends Bean<E> { }
|
Thus these declarations are invalid and would give the
compiler error. The Bean<E> must only have E (or any other letter used to
represent the type and cannot have extends keyword, or wildcard “?”
(we will encounter ‘?’ later) within the diamond.
// compiler
error – E is already declared to be a type of Fruit
public class MyBean<E extends Fruit> extends Bean<E extends Fruit> { }
// compiler
error – E is declared to be an unboubded type
public class MyBean<E> extends Bean<E extends Fruit> { }
|
What if we want to extend a generic class which already is
an upper bound, for instance NBean<E extends Number>?
public class MyNBean<E> extends NBean<E> { }
|
Pretty simple, Isnt’t it? No actually the above code would
not compile. This will give a compiler error: Bound mismatch: The type E is not
a valid substitute for the bounded parameter <E extends Number> of the
type NBean<E>. The Java programming language forces you to acknowledge
that the value E which you would be using should also be of the type Number.
This is because when using the class MyNBean you should not care which
class it extends. So the declataion must be made upfront so that the use of the
class knows which types can be used with this class. Thus the valid declaration
for the class MyNBean would be:
public class MyNBean<E extends Number> extends NBean<E> { }
|
Before we explore further, I’d like to mention that you can
have a class which is not typed but extends a typed class. In such a scenario
you must make the class that you are extending, an exact type.
public class IntBean extends MyNBean<Integer> { }
|
Let us refer back to the hierarchy of the Fruit class.
Remember there were 2 types of fruits – Grape & Mango, having 2 sub type
of each. If I have a bean called FruitBean<F extends Fruit>?
Can I extend it and make a typed class which only takes in Grape and not Mango?
Or maybe a MangoBean which only takes in a Mango type? Why not,
after all don’t we do the same thing? If the object takes in a Number,
I can still pass the object BigInteger and refer it as a Number.
The same argument applies here. The
class declarations below are valid.
public class FruitBean<F extends Fruit> extends Bean<F> { }
public class GrapeBean<F extends Grape> extends FruitBean<F> { }
public class MangoBean<F extends Mango> extends FruitBean<F> { }
|
The GrapeBean can be used to add any
type of grapes and the MangoBean will take in any Mango
(i.e. the classes inherited from the class Mango). Notice that the GrapeBean
& MangoBean extends FruitBean which is bounded.
The “extends” clause is meaningless if
the type extends a final class. Thus the declaration below would throw a compiler
warning because the class String is final, and so it cannot
be extended.
public class SBean<E extends String> extends Bean<E> { }
|
The warning message displayed by the compiler is - The
type parameter E should not be bounded by the final type String. Final types
cannot be further extended. Why not an error? Because you can still
initialize it only String object. Should you correct your code to the one below?
public class SBean<String> extends Bean<String> { }
|
No. Remember the earlier lesson? In fact this would give the
warning error again as - The type parameter String is hiding the type
String. Remember it is no different from the declaration below and
String here would be a type and not a class. Those guys working on java have
done their homework well.
public class SBean<E> extends Bean<E> { }
|
The correct decalaration would mean that you do not make the
class typed, as it will only take in the final class String. This is how it
should be declared.
public class SBean extends Bean<String> { }
|
Comments
Post a Comment