http://www.developer.com/

Back to article

Catch Errors at Compile Time with Java Generics


July 28, 2009

Imagine this scenario the day before releasing your software. While doing some final rounds of verification, your team discovers the nightmare of ClassCastException in the java.lang package. This typically occurs when you try to type cast entities of different types. For example, assigning a string to an integer will lead to this exception, thereby bringing the whole application to a standstill.

You can prevent such showstoppers if you use the generics feature introduced in J2SE 5. Using generics in your Java development can help you detect issues during compile time rather than being confronted by them at run time. This gives you greater control of the code and makes the syntax easy to follow. Generics also improves code readability.

Going back to the ClassCastException, does Java 5 convert the string value to an integer even though it is a non-integer? No. All possible data casting errors are determined during compile time. During compilation, it will ensure that you use the right types during object creation, and hence, Java 5 takes care of casting also. You do not have to explicitly cast the data every time you want to use them.

How Does the Code Work?

The code listings in this section detail the existing methodology and the generics method, and the discussion explains the differences between them.
Listing 1: Code Without Using Generics
 
import java.util.*;
 
public class NoGenerics
{
    public static void main(String args[])
    {
       NoGenerics noGenerics = new NoGenerics();
       noGenerics.initialize();
    }
 
    private void initialize()
    {
       List dataList = new ArrayList(); 
//Creating a new ArrayList to hold integers. //Even though the intention is to hold integer types,
there is no place to explicitly specify this dataList.add(new Integer(5)); //Adding an Integer object Integer value = (Integer) dataList.iterator().next();
//Retrieving the next value and assigning accordingly System.out.println("Retrieved value: "+value); } }

The coding style in Listing 1 is very common. Java developers have coded this way for a long time. However, as discussed previously, a problem can occur when you replace the the line dataList.add(new Integer(5)) with dataList.add(new String("5")). Listing 2 shows the code with this change.

Listing 2: Coding Without Using Generics Causes an Exception
 
import java.util.*;
 
public class NoGenericsWithExp
{
    public static void main(String args[])
    {
       NoGenericsWithExp noGenericsWithExp = new NoGenericsWithExp();
       noGenericsWithExp.initialize();
    }
 
    private void initialize()
    {
       List dataList = new ArrayList(); //Creating a new ArrayList to hold integers.
       //Even though the intention is to hold integer types, there is no place to explicitly specify this
       dataList.add(new String("5")); //Adding a String object
       Integer value = (Integer) dataList.iterator().next(); 
//Retrieving the next value and assigning accordingly System.out.println("Retrieved value: "+value); } }

Author Note: You need JDK version 1.5 or higher to compile for all further code listings.

When you try to run Listing 2, you end up with the following ClassCastException.

Just changing integer to string brought everything to a screeching halt. This is a real problem for developers who write code believing that their input will be controlled and passed as per the requirements, or who rely on sophisticated software to handle such showstoppers. The concept of generics makes your code less error prone in such scenarios.

Here is how you would handle the code example using generics:

Listing 3: Code Using Generics
 
import java.util.*;
 
public class Generics
{
    public static void main(String args[])
    {
       Generics generics = new Generics();
       generics.initialize();
    }
 
    private void initialize()
    {
       List<Integer> dataList = new ArrayList<Integer>(); //Creating a new ArrayList to hold integers.
       //Even though the intention is to hold integer types, there is no place to explicitly specify this
       dataList.add(new Integer("5")); //Adding a String object
       Integer value = dataList.iterator().next(); //Retrieving the next value and assigning accordingly
       System.out.println("Retrieved value: "+value);
    }
}

When you run Listing 3, the code will execute as expected.

To see the value that generics added, look carefully through the lines List<Integer> dataList = new ArrayList<Integer>(); and Integer value = dataList.iterator().next();. There is a newly added string in the code: <Integer>, which defines that the list is of type integer. By defining this, the compiler ensures that no other data type can be assigned to this list. If it encounters a problem, the compiler will let the developer know during compilation and the developer can fix it (in this case, by simply replacing the integer with a different data type).

If you look carefully, you'll notice another enhancement in the code. In the line Integer value = dataList.iterator().next();, the casting of the return value from the iterator is gone. The runtime is aware that you can assign only integer values and relieves the developer from that development hassle.

Code Readability

The following are examples of declaring objects to hold data:

//dataList will hold objects of type integer
List dataList = new ArrayList<Integer>(); 
//dataList will hold objects of type string
List dataList = new ArrayList<String>(); 
//dataList will hold objects of type MyObject
List dataList = new ArrayList<MyObject>();

The introduction of generics has greatly improved code readability. Anyone who uses the code understands what types of inputs are essential for the integration efforts. For example, when Listing 3 is rewritten as Listing 4 below, the compiler throws errors during compilation.

Listing 4: Code Using Generics and Has an Exception
 
import java.util.*;
 
public class GenericsWithExp
{
    public static void main(String args[])
    {
       GenericsWithExp genericsWithExp = new GenericsWithExp();
       genericsWithExp.initialize();
    }
 
    private void initialize()
    {
       List<Integer> dataList = new ArrayList<Integer>(); 
//Creating a new ArrayList to hold integers. //Even though the intention is to hold integer types,
//there is no place to explicitly specify this dataList.add(new String("5")); //Adding a String object Integer value = dataList.iterator().next();
//Retrieving the next value and assigning accordingly System.out.println("Retrieved value: "+value); } }

The compilation of Listing 4 fails with the following error message.

Looking into the error, it is clear that you can add objects of type integer only. Errors like these make it easy for the developers to correct their mistakes.

Generics Wildcards

If you do not know what type of data your collection is holding, then you can use a wildcard (<?>) in the placeholder for generics to indicate that the data type is unknown.

Assuming you have an ArrayList that has elements of unknown data types, you can simply employ the wildcard and start using the ArrayList in your application. Typical usage will be something like Listing 5.

Listing 5: Generics Using Wild Cards
 
import java.util.*;
 
public class GenericsWithWildCard
{
 
    public static void main(String args[])
    {
       GenericsWithWildCard genericsWithWildCard = new GenericsWithWildCard();
       List<Integer> intValueList = genericsWithWildCard.buildIntegerList();
       System.out.println("Sum of Integer arguments: " +(int)genericsWithWildCard.computeSum(intValueList));
       List<Double> doubleValueList = genericsWithWildCard.buildDoubleList();
       System.out.println("Sum of Double arguments: " +genericsWithWildCard.computeSum(doubleValueList));
    }
 
    private List buildIntegerList()
    {
       List<Integer> dataList = new ArrayList<Integer>();
       dataList.add(new Integer(12));
       dataList.add(new Integer(7));
 
       return dataList;
    }
 
    private List buildDoubleList()
    {
       List<Double> dataList = new ArrayList<Double>();
       dataList.add(new Double(11.4));
       dataList.add(new Double(6.3));
 
       return dataList;
    }
 
    private double computeSum(List<? extends Number> valueListArg)
    {
       double sum = 0;
       for (Object i : valueListArg)
       {
            if(i instanceof Integer)
            {
                        sum += (Integer) i;
            }
            else if(i instanceof Double)
            {
                        sum += (Double) i;
            }
            else
            {
                        System.out.println("Not an valid Integer/Double argument: "+i);
            }
       }
       return sum;
    }
}

If you look carefully, the computeSum() method takes an argument of List, which is of unknown type, extending Number. The method is used for achieving the same action for both types (integer and double) of arguments. Using the unknown type is a little tricky and may not be clear in all your use cases. See the generics documentation for more information.

Good programming does not mean that you need to use complex development techniques. The simple, easy-to-understand code that you can produce with generics will be maintainable by others, which can make everyone's life easier when issues arise while you're not on premise. Others can debug your code easily in your absence.

Code Download

  • Generics_ClassCastException.zip

    For Further Reading

  • Generics Documentation (from java.sun.com)

    About the Author

    Sridhar M S is a Java developer from Bangalore, India. He holds a master's degree in Computer Science.

  • Sitemap | Contact Us

    Thanks for your registration, follow us on our social networks to keep up-to-date