Java Generics - 7. Upper and Lower Bounds
This is part 7 of the 9 part series on Java Generics
Prev Topic
|
Topics
1. Introduction
2. Bounding
8. Target Types
|
|
Java Generics - Upper and Lower Bounds
This is going to be a really long blog, so please bear with me.Declaring Generic Variables
You can also use generics to declare the variables. In other
work you can choose which types can be assigned to a variable. The rules are
more or less the same as for methods. You cannot define a type variable, like
you could for the methods. Thus the declaration below makes no sense.
// invalid
private <E extends Fruit> E var1 = null;
// invalid
private <? extends Fruit> var2 = null;
|
It does make sense if the type E is declared at the class
level. Thus the ones below are all a valid declarations.
public class GenericVariables<E extends Fruit> {
E var = null;
public void foo(){
E localVar = null;
}
public E get(){
return var;
}
}
|
We have indirectly seen how variables are declared in the
code above. It is very similar to the way we declare the method parameters. Note
that the generics follow strict typing so a Bean<Fruit> cannot
be used to refer a Bean<Mango> even though Mango is a Fruit,
and Fruit
can be used to refer Mango.
// valid
Bean<Mango>
mBean1 = new Bean<>();
Bean<Mango>
mBean2 = new Bean<Mango>();
// Mango cannot be
initialized with Fruit and vice-versa
// Type mismatch:
cannot convert from Bean<Mango> to Bean<Fruit>
Bean<Fruit>
mBean3 = new Bean<Mango>(); // Error
// even though Fruit
can be used to refer Mango
Fruit f = new Mango(); // valid
// Error – this is invalid too
Bean<Mango> mBean4 = new Bean<Fruit>();
|
If you wish to refer to any type of Bean you can use
wildcard. If you wish to use any type of bean use the wildcard ‘?’
in the diamond – Bean<?>. If you want to refer any Mango, use Bean<?
extends Mango> type of reference. Again since all Fruit
cannot be Mango (they can be Grape too), the last declaration
gives compiler error.
Bean<?>
wBean1 = new Bean<>();
Bean<?>
wBean2 = new Bean<Fruit>();
Bean<?>
wBean3 = new Bean<Mango>();
Bean<?>
wBean4 = new Bean<Alphonso>();
Bean<?>
wBean5 = new Bean<Integer>();
Bean<? extends Mango> wmBean1 = new Bean<>();
Bean<? extends Mango> wmBean2 = new Bean<Mango>();
Bean<? extends Mango> wmBean3 = new Bean<Alphonso>();
// compiler error
// Type
mismatch: cannot convert from Bean<Fruit> to Bean<? extends
Mango>
Bean<? extends Mango> wmBean4 = new Bean<Fruit>();
|
The right side of the decaration cannot have a wildcard. The
programmers must be sure what type of objects they want to initialize. This is
the type of problem generics were created to solve in the first place. Thus in
the declaration below, the first one is valid, as anything which extends mango
is afterall a mango. Compare it with the wmBean3 variable above. The second
declaration is invalid because you are not specifying what would be the type of
Bean
which you are trying to initialize.
// valid
Bean<? extends Mango> wmBean5 = new Bean<Mango>();
// Cannot instantiate
the type Bean<? extends Mango>
Bean<? extends Mango> wmBean6 = new Bean<? extends Mango>();
|
These would be clearer when we discuss type erasure section.
Upper and Lower Bounding
We encountered earlier the use of wildcard with extends - <?
extends Fruit>. The variable having the Bean<? extends Mango>
can refer any Bean which if type Mango (i.e. the class Mango
and all classes extending Mango).
Bean<? extends Mango> wmBean1 = new Bean<>();
Bean<? extends Mango> wmBean2 = new Bean<Mango>();
Bean<? extends Mango> wmBean3 = new Bean<Alphonso>();
|
This is an example of upper bounding in Java. If you
initialize the bean with only Mango like Bean<Mango>, you
can only reference it with a Mango. Attachning an Alphonso
bean Bean<Alphonso>
would result in error. In the code below, the third line would throw an error
but not the last one. Staring at the code below will automatically make it
clear.
Bean<Mango>
mb = new Bean<>();
Bean<Alphonso>
ab = new Bean<>();
mb = ab; // error
Bean<? extends Mango> emb = new Bean<>();
emb = ab; // all is well
|
But wait !! This is not the only thing with upperbounding.
Consider this, you actually cannot call the method ‘set’ in the Bean<E>
class. You can do this with Java.
Bean<Mango>
mb = new Bean<>();
mb.set(new Mango());
mb.set(new Alphonso());
|
But the declaration
below would thow an error - The method set(capture#2-of ? extends Mango)
in the type Bean<capture#2-of ? extends Mango> is not applicable for the
arguments (Mango)
Bean<? extends Mango> meb = new Bean<>();
meb.set(new Mango()); // error
meb.set(new Alphonso()); // error
|
Now this is kind of strange – after all an Alphonso
is a Mango
(class Alphonso extends class Mango). Let us try this with the
class ArrayList. And we see that they too give an error when we try
to add values to the list of Number. The error is similar - The
method add(capture#4-of ? extends Number) in the type List<capture#4-of ?
extends Number> is not applicable for the arguments (Integer).
List<? extends Number> nList = new ArrayList<>();
Integer i = new Integer("4");
nList.add(i); // error
|
What is going on here? Well, that reason is, if you are
allowed to add an Integer as well as Double (both are afterall a Number)
after the code is complied and the type erased (coming later), then you would
not know what did you add unless you do a specific type checking (or type
casting) when you use it. You really won’t know what would be doing in the code.
It is allowing you to add different types in a list and generics were created
to avoid this problem in the first place. In fact it is quite similar to the
code without generics if it allows you to do it.
List nList = new ArrayList();
Integer i = new Integer("4");
nList.add(i); // no error, but no generics
|
The java compiler therefore prevents it. But you can use
this construct to fetch the values as a number as it would be clearly type
safe. Thus you can use this decalarion as a read-only list, where you can read
the elements as a number, but are not allowed to add to it. Thus the below
construct is actually allowed.
//
somewhere someone created a number list
// and you add all
types of number to it
List<Number>
nList = new ArrayList<>();
nList.add(new Integer(4));
nList.add(new Double(1.5));
nList.add(new Long(2000));
// and you can then
read it as <? extends Number> somewhere else
// preventing it from
being messed up by addition
// notice you can do
this assignment
List<? extends Number> exNumList = nList;
// you can read it as a
Number but not as Integer
Number n1 = exNumList.get(1);
Integer iNum = exNumList.get(1); // error - Type
mismatch: cannot convert from capture#3-of ? extends Number to Integer
|
Well, if we can get a read-only list, can I also have a
write-only one? Can there be a list that can be used to only write a value?
Yes. Welcome the keyword super.
public void writeOnly(List<? super Number> nList) {
// all are valid
nList.add(new Integer(4));
nList.add(new Double(1.5));
nList.add(new Long(2000));
// cannot read it
though :(
// error
Number
n = nList.get(1);
}
|
You might wonder, why will you use such a construct?
Actually it is quite handy when you are writing methods where you want to
prevent the values from being read. What if you want to both read and write to the
list? The answer is simple, just no not use wildcards.
public void readAndWrite(List<Number>
nList) {
// all are valid
nList.add(new Integer(4));
nList.add(new Double(1.5));
nList.add(new Long(2000));
// all are valid
Number
n1 = nList.get(1);
Number
n2 = nList.get(2);
}
|
The bounding with super is also known as lower
bounding. These upper and lower bounding can be used to the “in” and “out”
parameter in a method. Suppose you want to read some elements in a list and add
it to another. The best way to declare such a method is given below that avoids
errors in the code.
public static void copy(List<? extends Number> in, List<? super Number> out) {
for (Number n : in) {
out.add(n);
}
}
|
Difference between wild card and a typed element
What is the difference between using <? extends Fruit>
and <E
extends Fruit>? Which one should be used? In the above example we
saw that the two cases are equivalent and either can be used, but it is usually
not the case in all cases. Here are the nuggets of wisdom, distilled for you.
- Type parameter bounds can specify multiple bounds which you cannot do with a wildcard. Thus the declaration <E extends Fruit & Exportable> is valid but not <? extends Fruit & Exportable>
public <E extends Fruit &
Exportable> void doSomething1(List<E> e){
// do something
}
// error
public void doSomething2(List<? extends Fruit & Exportable> e){
// do something
}
|
- You cannot add elements with ‘extends’ wildcards – in the declaration below the second method declaration would give errors – something we have seen above.
public static <E extends Number> List<E>
createList1(E e1, E e2){
List<E>
ls = new ArrayList<>();
ls.add(e1);
ls.add(e2);
return ls;
}
public static List<? extends Number>
createList2(Number e1,
Number e2){
List<? extends Number> ls = new ArrayList<>();
ls.add(e1); //
error
ls.add(e2); //
error
return ls;
}
|
- You cannot have types with lowerbounds but only with wildcards. Thus the declaration <? super Fruit> is valid but <E super Fruit> is not.
- You should avoid using wildcard as the method return type. This sometimes forces the users to use wildcards in the objects being declared. Just follow a simple rule – use wildcards only as method parameters and the typed parameters as the return types so that the user of your API does not have to worry about wildcards. Wildcards should generally be used by the API programmer and not by the API user. Consider the code below for the two createList methods declared above. The method which returns a wildcard should be accepted by a variable which has a wildcard. The only reason you’s like to return a wildcard in the method, is to lock it for further addition (remember the upperbounding constraints). If java would have allowed the third declaration to be valid then we could add elements to the list with the upperbound constrains which as we explored above is not right.
List<Number> nList1 = createList1(3, 4);
List<? extends Number> nExList1 = createList2(3, 4);
// invalid - compiler error
// Type mismatch: cannot convert from List<capture#3-of ?
extends Number>
// to List<Number>
List<Number> nList2 = createList2(3, 4);
// valid
List<? extends Number> nExList2 = createList2(3, 4);
|
Prev Topic
|
Topics
1. Introduction
2. Bounding
8. Target Types
9. Type Erasure
|
|
Comments
Post a Comment