Understand the inner workings of generics in Java

Generics are extremely subtle and difficult. Most of the quirky features of generics derive from the fact that they are a purely compile time feature. This article explains how generics work and discusses features of generics, including autoboxing, variable-length argument lists, and wildcards.

Generics, an advanced feature of Java 1.5, allow type checking to make sure you fill your Collections and ArrayLists with only the types of Object you intend. Generics also check that you are using the right sort of Comparator for the objects you are sorting. This type checking happens mostly at compile time. (C++ has something similar called templates.)

How generics work

All of the Java Collection classes (e.g., HashMap, ArrayList, and TreeList) only contain generic Objects. The problem with this is that you must manually keep track of what sorts of Objects are in each Collection. For example, the compiler won't warn you if you add a Cat to a Collection of Dogs. Java 1.5 has generics implemented with high overhead, requiring implicit casts both on the way into the collection and on the way out. The compiler automatically casts all objects going into your collection to Dog (for example) and will automatically cast them back to Dog on the way out. Inside the collection, they are treated as generic Objects. In theory, neither of these two time-consuming casts are necessary, yet removing them would create a vulnerability in the JVM that could be exploited maliciously.

The new syntax and autoboxing

Generic Java classes can only use Objects as formal type parameters; List<Integer> will compile but List<int> cannot. Thanks to autoboxing, you can add a primitive int to List<Integer>. Autoboxing is the term for treating a primitive type as an object type without any extra source code. The compiler automatically supplies the extra code needed to perform the type conversion. For example, as of J2SE 5.0, Java allows you to create a LinkedList of ints. The LinkedList only lists objects; it cannot list primitive types. Now when Java expects an object but receives a primitive type, it immediately converts that primitive type to an object.

This action is called autoboxing because boxing is done automatically and implicitly instead of requiring you to manually do so. Unboxing refers to a boxed primitive type that has been broken down, and the primitive type retrieved for a process of some kind such as a mathematical operation. This is an example of code using generics and autoboxing features:

int z = 5;

List<Integer> digits = new Vector<Integer>();

Integer $int = (z * 32); // Boxing

digits.add(++$int); // Unboxing

$int = digits.get(0); // Boxing

boolean is33 = $int == 33; // Unboxing

Generics are checked at compile time for type correctness. The generic type information is then removed via a process called type erasure; the generic type information is retained only for superclass. For example, List<Integer> will be converted to the raw type (non-generic type) List, which can contain arbitrary objects. Due to the compile-time check, the resulting code is guaranteed to be type correct as long as the code generated no unchecked compiler warnings.

Variable-length argument lists

Another cool feature in C/C++ but lacking in Java before version 1.5 is variable-length argument lists. These arguments are resolved as a primitive Array. The calls in the main method are all valid for the exact same method declaration. This is an example of the new syntax:

public static void showMessages(String ... args){

for(String arg : args)

System.out.println("Message: "+arg);

}

public static void main (String ... args){

showMessages("hello world");

showMessages("potatoes", "tomatoes");

showMessages("mother", "sister", "father");

showMessages("Ryan", "Lacy", "Tara", "Chad");

}

Wildcards

Generic type parameters in Java are not limited to specific classes. Java allows the use of wildcards to specify bounds on the type of parameters a given generic object may have. Wildcards are type parameters of the form "?," possibly annotated with a bound. Given that the exact element type of an object with a wildcard is unknown, restrictions are placed on the type of methods that may be called on the object.

As an example of an unbounded wildcard, List<?> indicates a list that has an unknown object type. Methods that take such a list as an argument can take any type of list regardless of parameter type. Reading from the list will return objects of type Object, and writing non-null elements to the list is not allowed since the parameter type is not known.

The extends keyword is used to specify the upper bound of a generic element. Extends indicates that the generic type is a subtype of the bounding class, so it must either extend the class or implement the interface of the bounding class. List<? extends Number> means that the given list contains objects that extend the Number class. For example, the list could be List<Float> or List<Number>. Reading an element from the list will return a Number, while writing null elements to the list is not allowed.

The super keyword is used to specify the lower bound of a generic element. The super keyword indicates that the generic type is a supertype of the bounding class. List<? super Number> could be List<Number> or List<Object>. Reading from the list returns objects of type Object. Any element of type Number can be added to the list since it is guaranteed to be a valid type to store in the list.

Conclusion

Generics are extremely subtle and difficult. Most of the quirky features of generics derive from the fact that they are a purely compile time feature. They leave almost no trace in the run time code; this is called type erasure. For example, if you decompile ArrayList (which is crawling the generics in the source), there is no sign of the generics in the class file — it just stores and retrieves ordinary Objects without any casting. The only sign that generics were ever involved are the casts automatically inserted on the gets in the caller to convert the Objects to the types of receiving variables. There is no casting code in ArrayList itself. This is why generic code can't do obvious things like construct an object of type T or cast an object to type T.