Introduction to Generics in Java
By definition, generic programming is a feature of programming language that allows methods or functions to work on generic types that will be specified later. The feature was fist introduced in Java by Phillip Walder in 1998 and later officially included as a component of the Java language in 2004.
Instead of working with Object, generics add a way to specify types for classes and methods. To demonstrate the concept, we are going to look at some code using Collection in Java.
Suppose, we intended to store many integers into an ArrayList of type Object. Mistakenly, a String was added to it. The problem occurs when we attempt to retrieve the added String and cast it to an Integer.
List numbers = new ArrayList();
numbers.add(1);
numbers.add(2);
// mistakenly
numbers.add("test");
// read the numbers
for (int i = 0; i < numbers.size(); i++) {
Integer n = (Integer)numbers.get(i);
// do something with n
}
The code compiles well. However, at run time, the program throws a ClassCastException exception:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
One way to get rid of the problem is to use a try/catch block
List numbers = new ArrayList();
numbers.add(1);
numbers.add(2);
// mistakenly
numbers.add("test");
// read the numbers
for (int i = 0; i < numbers.size(); i++) {
try {
Integer n = (Integer)numbers.get(i);
// do something with n
} catch (ClassCastException e) {
System.out.println("Unexcepted type " + e.getMessage());
}
}
And the exception is caught:
Unexpected type java.lang.String cannot be cast to java.lang.Integer
Generics allows you to even 'catch' this type of exception at compile time. By specifying the type of the elements, similar mistake are avoided.
List<Integer> numbers = new ArrayList<Integer>();
numbers.add(1);
numbers.add(2);
// mistakenly
numbers.add("test");
The compiler says that:
The method add(Integer) in the type List<Integer> is not applicable for the arguments (String)
So, it does not accept a String as a member of an ArrayList of Integer.
Generic Method
Generic methods are methods that introduce their own generic types, whose scope is limited to the method. The syntax for a generic method includes a type parameter, inside angle brackets, and appears before the method's return type.
Here is an example. We declare printlist a generic method. The type is used in List<T>
and for (T n:numbers)
// File GenericMethodTest
public class GenericMethodTest {
public static <T> void printList(List<T> numbers) {
for (T n : numbers) {
System.out.println(n);
}
}
public static void main(String []args) {
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
printList(ints);
}
}
The above code produce:
1
2
3
Type Wildcard
Consider the following code:
public static void printSquare(List<Number> numbers) {
for (Number n : numbers) {
double d = n.doubleValue();
System.out.println(d * d);
}
}
The printSquare method is intended to take an argument of type List<Number>.
Yet, we want to use subclasses of Numbers such as Integer, Float or Double. Attempts to call the method like the followings will result in compile errors:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
printSquare(ints);
The compiler says:
The method printSquare(List <Number>) in the type GenericsTest is not applicable for the arguments (List <Integer>)
To deal with this situation, Java allows us to specify the type of the List elements as a subclass of Number. The ? mark follows by the extends keywords tell the compiler that the type argument is a subtype of the bounding class (Number). List<? extends Number>
Replace the wildcard into the above printSquare method, we have the following runnable code.
// file TypeWidecardTest
public class TypeWildcardTest {
public static void printSquare(List<? extends Number> numbers) {
for (Number n : numbers) {
double d = n.doubleValue();
System.out.println(d * d);
}
}
public static void main(String []args) {
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
printSquare(ints);
}
}
The above code produces:
1
4
9
Generic Class
A generic type is a generic class or interface that is parameterized over types. The following Box class will be modified to demonstrate the concept. Here is an example of a generic Java class. The Entry classes resemble entries found in Dictionary:
// File Entry.java
public class Entry<KeyType, ValueType> {
private final KeyType key;
private final ValueType value;
public Entry(KeyType key, ValueType value) {
this.key = key;
this.value = value;
}
public KeyType getKey() {
return key;
}
public ValueType getValue() {
return value;
}
public String toString() {
return "(" + key + ", " + value + ")";
}
}
The Entry class may be used in the following ways:
// File GenericClassTest.java
public class GenericClassTest {
public static void main(String []args) {
Entry<String, String> phone = new Entry<String, String>("Alice", "123456789");
Entry<String, Integer> height = new Entry<String, Integer>("Alice", 167);
System.out.println("Phone number: " + phone);
System.out.println("Height: " + height);
Entry<String, Boolean> isMarried = new Entry<String, Boolean>("Alice", true);
if (isMarried.getValue()) System.out.println(isMarried.getKey() + " is married.");
else System.out.println(isMarried.getKey() + " is not married.");
}
}
The above code produces:
Phone number: (Alice, 123456789)
Height: (Alice, 167)
Alice is married.
Summary
- Generics add a way to specify types for classes and methods
- We can provide parameterized types for generic classes and methods in Java.
- To limit parametrized types based on inheritance relationship, we can use type wildcard.