Java Generics - 2. Bounding

This is part 2 of the 9 part series on Java Generics 



Prev Topic

Topics



Next Topic



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 GrapeChardonnay and Shiraz (these classes extend the class Grape). Similarly there are two types of MangoAlphonso 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

Popular posts from this blog

Java Generics - 7. Upper and Lower Bounds

Java Functional Programming : 3. The java.util.function Library

Java Generics - 3. Multiple bounding