3 Reasons Why You Should Use Java Generics

If you are a Java developer aspiring to learn the concepts of Generics or want to revise your knowledge of generics in most enjoyable way possible then look no further.

 

 

After reading this article, you will be able to explain your friends and colleagues why Generics is important, what is it all about and when to use generics. You will also learn the best practices to apply this concept to your work and personal projects.

In order to truly realise the benefits generics bring to our coding life, we have to first understand the pains we had to go through in pre generics world. In the following section I will walk you through some code examples implemented with and without generics so that you can clearly understand the benefits of generics.

 

In Java applications we frequently use classes and interfaces from collection framework to manipulate data sets. Collection framework provides several inbuilt classes to help us work with common data structures like lists, sets, maps etc.

 

Though collection framework was designed to work with any kind of object, for most of our real life applications we generally work with collections of specific object types or object families (read sub classes).

 

Normally we perform operations on objects which are related to each other in some way.

 

Let us go through an example method where we have a list of ‘Cat’s and we need to find the white coloured ones. First we will write the program without using generics:

 


public void findWhiteCats(){

   //Create Cats of different colours
   Cat puss = new Cat(Colour.WHITE,"Puss");
   Cat oscar = new Cat(Colour.BROWN,"Oscar");
   Cat kitty = new Cat(Colour.BLACK,"Kitty");
   Cat smokey = new Cat(Colour.WHITE,"Smokey");

   //Create an ArrayList and add all the Cats
   List myCats = Arrays.asList(puss,oscar,kitty,smokey);

   Iterator iter = myCats.iterator();

   while(iter.hasNext()){
       //Notice the explicit cast
       Cat thisCat = (Cat)iter.next();

       if(Colour.WHITE.equals(thisCat.getColour()))
           System.out.println(thisCat.getName()+ " is a white cat!");
   }
}

 

Before generics were introduced in Java 5, there was no way of specifying which type of object is intended for a specific collection. As classes from collection framework accepts any object, an API user could put any object type in a collection.  So, we could put a ‘Car’ object in a collection that was intended to hold Cats without any compilation error. This type of error may result in ClassCastException at run time.

 

‘Cat’s have different set of methods to support behaviours relevant to them. In order to invoke methods specific to ‘Cat’ objects, one needed to explicitly cast the object received from collection to ‘Cat’. In the code example above, we have explicitly casted the object to ‘Cat’ in order to invoke getColour() method.

 

This explicit casting will work as long as we only add ‘Cat’ objects in the myCats list. Otherwise, we will get ClassCastException at runtime.

 

So, we need thorough code reviews to ensure we have added intended object types to the collection. But, only review can’t guarantee that our code will not break at runtime.

 

Please remember that in most of the cases other client programs will create the list of objects and use methods written by you to do some processing on the list. So, you won’t have control on which object types are added to the list (technically you can use instanceof operator to validate the data type, but that will lead to extra boilerplate coding).

 

Also the boilerplate castings make the code less readable.

 

These inherent problems inspired the creators of java to bring the concept of generics in Java 5. Since then the features have been improved through versions 7 and 8.

Now let us see the reworked code using generics:

 


public void collectionWithGenerics(){

   Cat puss = new Cat(Colour.WHITE,"Puss");
   Cat oscar = new Cat(Colour.BROWN,"Oscar");
   Cat kitty = new Cat(Colour.BLACK,"Kitty");
   Cat smokey = new Cat(Colour.WHITE,"Smokey");

   //Create an ArrayList and add all the Cats
   List<Cat> myCats = Arrays.asList(puss,oscar,kitty,smokey);

   for(Cat thisCat:myCats){
       if(Colour.WHITE.equals(thisCat.getColour()))
           System.out.println(thisCat.getName()+ " is a white cat!");
   }
}


 

Notice that we have passed the intended object type within the diamond notation List<Cat> when declaring the list myCats. When this notation is used compiler will not allow any object type other than ‘Cat’ to be added to the list.

 

Another compelling reason for using generics is that it allows us writing generic algorithms that support related objects. Let me explain the concept using some code example.

 

For this example, I have created an Animal class which is the superclass for other specific animal types. I have also created a Dog and a Cat class both of which are subclasses of Animal class.

 

 

 


package com.programmingtochange.corejava.generics;

public class Animal {

    private Double averageWeight;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private Double getAverageHeight;

    private String name;

    public Double getAverageWeight() {
        return averageWeight;
    }

    public void setAverageWeight(Double averageWeight) {
        this.averageWeight = averageWeight;
    }

    public Double getGetAverageHeight() {
        return getAverageHeight;
    }

    public void setGetAverageHeight(Double getAverageHeight) {
        this.getAverageHeight = getAverageHeight;
    }

    public String getClassification() {
        return classification;
    }

    public void setClassification(String classification) {
        this.classification = classification;
    }

    private String classification;

    public Colour getColour() {

        return colour;
    }

    public void setColour(Colour colour) {
        this.colour = colour;
    }

    private Colour colour;

    public String toString(){
        return "This is a " + getClassification() +" named "+ getName() + " coloured " + getColour();
    }

}


package com.programmingtochange.corejava.generics;

public class Dog extends Animal {

    public Dog(Colour colour, String name) {
        this.setColour(colour);
        this.setName(name);
        this.setClassification("Dog");
    }

}


package com.programmingtochange.corejava.generics;

public class Cat extends Animal {

    public Cat(Colour colour, String name) {
        this.setColour(colour);
        this.setName(name);
        this.setClassification("Cat");
    }

}



 

Now assume we need a method for comparing two animals and provide some insightful information about how closely are they related. The code snippet below is almost self-explanatory. I will only explain how generics is used to make the code clutter free, more readable and type safe.

 

 

 


/**
 * Compares whether animals passed in input has same classification, colour and name
 * @param animal1
 * @param animal2
 * @param &amp;amp;amp;amp;amp;lt;T&amp;amp;amp;amp;amp;gt;
 */
public &amp;amp;amp;amp;amp;lt;T extends Animal&amp;amp;amp;amp;amp;gt; void compareAnimals(T animal1, T animal2){

    System.out.println("Comparing....");
    System.out.println("Animal 1: " + animal1);
    System.out.println("Animal 2: " + animal2);

    if(animal1.getClassification().equals(animal2.getClassification())
    &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; animal1.getColour().equals(animal2.getColour())
    &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; animal1.getName().equals(animal2.getName())){
        System.out.println("Both animals are equal!");
    }else if(animal1.getClassification().equals(animal2.getClassification())
            &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; (!animal1.getColour().equals(animal2.getColour())
            || !animal1.getName().equals(animal2.getName()))){
        System.out.println("Both animals are " + animal1.getClassification() + " but they have different colour or name");
    }else if(animal1.getColour().equals(animal2.getColour())
            &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; (!animal1.getClassification().equals(animal2.getClassification())
            || !animal1.getName().equals(animal2.getName()))){
        System.out.println("Both animals have same colour " + animal1.getColour() + " but they have different classification or name");
    }else if(animal1.getName().equals(animal2.getName())
            &amp;amp;amp;amp;amp;amp;&amp;amp;amp;amp;amp;amp; (!animal1.getClassification().equals(animal2.getClassification())
            || !animal1.getColour().equals(animal2.getColour()))){
        System.out.println("Both animals have same name " + animal1.getName() + " but they have different classification or colour");
    }
}


 

First thing you will notice in the method declaration above is the <T extends Animal> syntax used. Here ‘T’ is the generic type parameter used in the method. T extends Animal sets an upper bound on the allowable types for T to class Animal. So, T must me some sub class of Animal. Otherwise, compiler will give error.

 

If you see the arguments used in method compareAnimals(), they are the generic type ‘T’ rather than any specific object type. As we have set an upper bound of class Animal for parameter ‘T’, within the method body we are free to use any methods declared in Animal class.

 

If instead of using the upper bound we simply used parameter ‘T’, only methods available in parent class ‘Object’ would have been available to us. Obviously that could not solve desired outcome for this method.

 

Apart from the generics bit rest of the method implementation is pretty straight forward. We just compared two Animal instances passed as input and compared whether they are same or if not then what is the difference.

 

If you notice carefully, there is no type casting in the whole code to convert from ‘T’ to Animal. Everything is taken care of by the compiler and there is no chance of getting a class cast exception.

 

Now let us write some code to test our method by invoking the method with different Animal instances.

 

 


public void testCompareAnimals(){

    Cat puss = new Cat(Colour.WHITE,"Puss");
    Cat oscar = new Cat(Colour.BROWN,"Oscar");
    Cat kitty = new Cat(Colour.BLACK,"Kitty");
    Cat smokey = new Cat(Colour.WHITE,"Smokey");
    Cat tom = new Cat(Colour.BLACK,"Tom");

    Dog pluto = new Dog(Colour.WHITE,"Pluto");
    Dog max = new Dog(Colour.BROWN,"Max");
    Dog buddy = new Dog(Colour.BLACK,"Buddy");


    //Compare same animals
    compareAnimals(puss,puss);

    //Compare different animal colours
    compareAnimals(puss,oscar);

    //Compare different animal names
    compareAnimals(puss,smokey);

    //Compare different animal types
    compareAnimals(puss,pluto);

}



 

I think the code is self-explanatory. When we run the code we get following output in console:

 

Comparing….

Animal 1: This is a Cat named Puss coloured WHITE

Animal 2: This is a Cat named Puss coloured WHITE

Both animals are equal!

 

Comparing….

Animal 1: This is a Cat named Puss coloured WHITE

Animal 2: This is a Cat named Oscar coloured BROWN

Both animals are Cat but they have different colour or name

 

Comparing….

Animal 1: This is a Cat named Puss coloured WHITE

Animal 2: This is a Cat named Smokey coloured WHITE

Both animals are Cat but they have different colour or name

 

Comparing….

Animal 1: This is a Cat named Puss coloured WHITE

Animal 2: This is a Dog named Pluto coloured WHITE

Both animals have same colour WHITE but they have different classification or name

 

Following table summarizes the benefits of using generics:

 

Feature Description
Type check at compile time Compiler ensures type safety at compile time. If your code gets compiled correctly then you will not get any cast exception at runtime.
No explicit casts required Elimination of explicit casts increases the readability of your code.
Generic algorithm implementation Generic algorithms can be implemented in a single method with generic types as input instead of separate methods to support different object types or unnecessary casts.

 

 

That’s all for this article. I intend to publish a sequel of this post covering more advanced topics of generics like usage of wildcards, upper bounds, lower bounds, multiple bounds, raw types, erasure etc. Please let me know in the comments if you are interested and stay tuned for more useful topics.

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Positive SSL