Java Generics - 1. Introduction

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

If someone asks me - what are the best features of Java programming since the release of 1.5? I will vouch for two of them - Generics and Annotations. What makes Generics so special is the compile time checking of a type, the restrictions on the usage of a class it provides and simply its elegance. What amazes me however, is that the programmers rarely use the full power of Generics that Java programming language provides. So I thought of writing down a series of blogs that can be used by the programmers to understand and use them. There are a series of 9 blogs on the topic. They were too large to be adjusted into just one. So, just like in programming, I decided to 'divide and conquer'.






Topics



Next Topic



Java Generics - Introduction


Generics are type checks during compilation. They add to the stability and clarity of your Java code. As a developer you must strive to write typed classes in case of need and always use generics for a typed class (such as the classes extending collections and maps). Before we proceed further let us look at a class called OBean which holds just an element with getter and setter values, and explore why this is a potential problem during runtime and if handled better with compiler checks can save us future pangs.


  public class OBean {

       private Object e;

       public void set(Object e){
              this.e = e;
       }
      
       public Object get(){
              return e;
       }            
  }


Using this will involve memory of the input object type, when retrieving the value using the get method of the class. Thus its usage would be something like this.


              OBean b = new OBean();
              b.set(new Integer(5));
              Integer i = (Integer)b.get();
              System.out.println("Value=" + i);


It tells us that we can put any object in the class, but when using we need to apply a proper cast. So the programmer must remember or reference the code for the type used in the set method, when using the get method. Incorrect cast can throw a runtime exception. What if the programmer did this?


              // can I do this?
              String s = (String)b.get();       // Yes
              System.out.println("Value=" + s); // Throws a ClassCastException


Well we would get a ClassCastException during runtime. The compiler would give no signal of its potential threat during compilation. Wouldn’t it be nice to have the compiler check for such potential threats and let the programmer know about its dangers? Or better still, wouldn’t it be better to disallow compilation of the above code? Well that’s what generics are, and much much more as we shall se as we progress further. To allow the compiler detect these problems the OBean class should be made generic to allow us to insert only one type and retrieve only that type. Let us see how such a class can be written. Declare a generic class Bean. Note that a letter E is enclosed between the symbols < and >. <E> actually means any element.


  public class Bean<E> {
      
       private E e;

       public void set(E e){
              this.e = e;
       }
      
       public E get(){
              return e;
       }     
  }


Not much of a difference. The object e of type Object is now replaced by the letter E. Using the object of this class is even simpler.


              Bean<Integer> bi = new Bean<Integer>();
             //Bean<Integer> bi = new Bean<>(); // Java 1.7 declaration
              bi.set(new Integer(6));
              Integer i2 = bi.get();     // no cast needed
              System.out.println("Value=" + i2);


Note that you needn’t cast the object when you are retrieving the values back. This solves the problem of casting. The java compiler also checks for improper reference to another type. Thus the code in the section below would give a compiler error. This means a programmer cannot go wrong when accessing a typed object.


            String s2 = bi.get();     // gives an error during compilation            
            bi.set("Hello");          // so will this


A quick word about the declaration – the declaration on the left is matching the declaration at the right (<Integer>). There are other ways of declaring too, and we will check those as we move forward. But this is by far the most common way of declaring. So java 1.7 came up with a shortcut. It allowed the user to forgo the type at the right side, allowing the compiler to do that thinking on its own. Thus we can simply use the diamond (i.e. the symbol < >) on the right side.


           Bean<Integer> bi = new Bean<>(); // Java 1.7 declaration


Java could have eliminated the diamond completely on the right side, but it chose instead to retain it, maybe to let the programmer know that generics are being used.
Coming back to the Bean class’s declaration, we typed the class with the letter E within the diamond < >, as <E>. Could we use <T> instead? Yes why not, after all they mean the same thing. How about lowercase E or T – like <e>, <t>? The answer is again yes. But you should not use lowercase as a type declaration to avoid confusion, as much as you must avoid declaring classes beginning with lowercase. Can you use more than one letter to mean a type, say <Element> instead of <E>? The answer is again, yes. But remember, that this will cause confusion when you are using it as Element might represent a class as well. As a good programmer you will not usually declare a class with a single uppercase letter and this might help in keeping the distinction. In fact the letters should signify something meaningful. As a guideline you can use the table below when declaring the type element of a generic class. Though not very strict, java suggests these naming conventions.

Generics Naming Convention
Type
Meaning
E
Element (as in Collections)
K
Key (as in Maps)
V
Value (as in Maps)
N
Number
T
Type
S, U, etc
Any other type

Remember anything declared within the diamonds is a type, and not a class. Say you only wanted to define a class which takes in only the Integer type, can you declare the class as, to use it only for Integer?
public class Bean<Integer> { … }
The answer is yes and no. Yes you can declare but then it would not mean that the type can only be of the type Integer, but would mean the type should be referred with the name Integer (instead of E). It will take any type while initialising. Besides, if you only want the class to take in values of type Integer, you need not define your class as a typed one, rather as a strict one without a type. Let’s have a funny example, and the reason why I said earlier that you should avoid Strings as type names and stick only with single uppercase letter. Let us declare a class called Improper.


  public class Improper<Integer> {
      
       public Integer i;
      
       public void put(Integer i){
              this.i = i;
       }
      
       public Integer get(){
              return i;
       }
 }


Is this a valid declaration? Yes of course it is. Does it mean it will take in only Integer? No! Of course, not. In fact it is no different than the declaration of class Bean<E>. Here the string Integer means any type, represented by the string “Integer”. Let’s check the use.


              Improper<String> im = new Improper<>();
              im.put("Hello");
              String s = im.get();
              System.out.println("Value=" + s);


Is this usage proper? Yes it is again. Instead of simplifying the problem, it in fact added to confusion. In fact the compiler gives you a waring when you declare this class – “The type parameter Integer is hiding the type Integer”. So remember the thumb rule for type declaration in a class – always use the uppercase single letter, which makes sense. The letters such as <O> and <I> don’t express themselves better as they appear to be the number zero and one to the untrained eye. Of course you cannot use numbers within the diamonds like <3>.

You might wonder what if I have a class that supports multiple types? Should I use strings to define a type when I exhaust the reasonable uppercase letters? The answer is still, no. If you have so many parameter types then you are adding confusion to the declaration as well as its usage. Do not declare a class having more than 3 types. Ok, maybe 4 just in case of dire need. But that’s where you should stop. If more types are needed, then you must inspect the structure of your class and split it properly. Let us see a quick example of multiple type declaration. In fact you would have used it when using the java Map class, which is defined by 2 types. Here is a class Pair which takes in 2 types – represented by L (left) and R (right)


  public class Pair<L, R> {

       private L left;
       private R right;
      
       public void put(L left, R right){
              this.left  = left;
              this.right = right;
       }
      
       public L getLeft(){
              return left;
       }
      
       public R getRight(){
              return right;
       }                         
  }


The usage is predictable. The rule is simple; whatever type is used for L and R only those types are allowed in the place holders. 


       Pair<String, Integer> siPair = new Pair<>();
       siPair.put("Hello", new Integer(7));
       String left   = siPair.getLeft();
       Integer right = siPair.getRight();
       System.out.println("Left=" + left + ", Right=" + right);


If the type is defined for a class (or Interface), then it can accept all the types that type extends (or implements). Since all classes in java are derived from Object, any class which is defined with the type Object will take in any object. But the catch is that when such classes have methods which return the type values, then you’d get them back only as an instance of Object. Here is a demonstration of that code with the Bean<E> class by initializing it with Number and adding objects which are of type Number (or extended from Number). Remember that the classes Integer, BigInteger & Double all extend the abstract class Number.


              Bean<Number> bn = new Bean<>();
              // I can add anything which is of type Number (extends Number)
              bn.set(new Integer(5));
              bn.set(new BigInteger("4466"));
              bn.set(new Double(7.75d));
              // This is type safe as you can read it as a Number
              Number n = bn.get();
              Integer i = bn.get(); // compiler error


You can set any Number, but when you are reading it back you can only read it as a Number. This preserves the type safety of the returned value. In the above example, taking the return value of the get( ) method as an integer would give compiler error. Of couse you have can cast the retrun value to the number, but then you have just defeated the cause why generics exist in the first place. No prizes for guessing that the class Pair<L, R> would work similarly.
By now, you would have understood the basic concept of the creation and usage of generics. But this is just the tip of the iceberg. Java’s generics can do a lot many things and has a lot of other features which will be useful for the programmers. Let’s explore deeper. What if you want only a particular type of elements to be used by the generic class? What if you want to initialize the class only with say a Number type, i.e. the classes which extend the class Number?




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