Generics enable you to detect errors at compile time rather than at runtime. You have used a generic class ArrayList and generic interface Comparable. Generics let you parameterize types. With this capability, you can define a class or a method with generic types that the compiler can replace with concrete types. For example, Java defines a generic ArrayList class for storing the elements of a generic type. From this generic class, you can create an ArrayList object for holding strings and an ArrayList object for holding numbers. Here, strings and numbers are concrete types that replace the generic type.
The key benefit of generics is to enable errors to be detected at compile time rather than at runtime. A generic class or method permits you to specify allowable types of objects that the class or method can work with. If you attempt to use an incompatible object, the compiler will detect that error.
We'll explains how to define and use generic classes, interfaces, and methods and demonstrates how generics can be used to improve software reliability and readability.
Motivations and Benefits
The motivation for using Java generics is to detect errors at compile time. Java has allowed you to define generic classes, interfaces, and methods since JDK 1.5. Several interfaces and classes in the Java API were modified using generics. For example, prior to JDK 1.5 the java.lang.Comparable interface was defined as shown in Figure below (a), but since JDK 1.5 it is modified as shown in Figure below (b).
Here, represents a formal generic type, which can be replaced later with an actual concrete type. Replacing a generic type is called a generic instantiation. By convention, a single capital letter such as E or T is used to denote a formal generic type.
To see the benefits of using generics, let us examine the code in Figure below. The statement in Figure below (a) declares that c is a reference variable whose type is Comparable and invokes the compareTo method to compare a Date object with a string. The code compiles fine, but it has a runtime error because a string cannot be compared with a date.
The statement in Figure above (b) declares that c is a reference variable whose type is Comparable and invokes the compareTo method to compare a Date object with a string. This code generates a compile error, because the argument passed to the compareTo method must be of the Date type. Since the errors can be detected at compile time rather than at runtime, the generic type makes the program more reliable.
The ArrayList Class has been a generic class since JDK 1.5. Figure below shows the class diagram for ArrayList before and since JDK 1.5, respectively.
For example, the following statement creates a list for strings:
ArrayList<String> list = new ArrayList<>();
You can now add only strings into the list. For instance,
list.add("Red");
If you attempt to add a nonstring, a compile error will occur. For example, the following statement is now illegal, because list can contain only strings.
list.add(new Integer(1));
Generic types must be reference types. You cannot replace a generic type with a primitive type such as int, double, or char. For example, the following statement is wrong:
ArrayList<int> intList = new ArrayList<>();
To create an ArrayList object for int values, you have to use:
ArrayList<Integer> intList = new ArrayList<>();
You can add an int value to intList. For example,
intList.add(5);
Java automatically wraps 5 into new Integer(5). This is called autoboxing, as introduced in Automatic Conversion between Primitive Types and Wrapper Class Types.
Casting is not needed to retrieve a value from a list with a specified element type, because the compiler already knows the element type. For example, the following statements create a list that contains strings, add strings to the list, and retrieve strings from the list.
1 ArrayList<String> list = new ArrayList<>();
2 list.add("Red");
3 list.add("White");
4 String s = list.get(0); // No casting is needed
Prior to JDK 1.5, without using generics, you would have had to cast the return value to String as:
String s = (String)(list.get(0)); // Casting needed prior to JDK 1.5
If the elements are of wrapper types, such as Integer, Double, and Character, you can directly assign an element to a primitive type variable. This is called autounboxing, as introduced in the link above. For example, see the following code:
1 ArrayList<Double> list = new ArrayList<>();
2 list.add(5.5); // 5.5 is automatically converted to new Double(5.5)
3 list.add(3.0); // 3.0 is automatically converted to new Double(3.0)
4 Double doubleObject = list.get(0); // No casting is needed
5 double d = list.get(1); // Automatically converted to double
In lines 2 and 3, 5.5 and 3.0 are automatically converted into Double objects and added to list. In line 4, the first element in list is assigned to a Double variable. No casting is necessary, because list is declared for Double objects. In line 5, the second element in list is assigned to a double variable. The object in list.get(1) is automatically converted into a primitive type value.