Software Development optimize

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.

Additional resources about generics

Peter V. Mikhalenko is a Sun certified professional who works for Deutsche Bank as a business consultant.

---------------------------------------------------------------------------------------

Get Java tips in your inbox Delivered each Thursday, our free Java newsletter provides insight and hands-on tips you need to unlock the full potential of this programming language. Automatically subscribe today!
6 comments
Justin James
Justin James

Good stuff. Other than the type checking, Comparator enforcement, autoboxing, and "extends" keyword, what are the advantages to using a Java generic over a different collection type? Since it is a compile-time feature, it does not deliver any run time benefits. J.Ja

pletisan
pletisan

When You say "writing non-null elements to the list is not allowed" does it mean You can not write anything but null value to the List ? Or I am missing something...

teksty32
teksty32

nice article. Message was edited by: beth.blakely@...

jslarochelle
jslarochelle

...you put type information in code with a minimum of overhead. Instead of: // A list of String List messages = new ArrayList(); You can use: List messages = new ArrayList() Of course it would not be worth it without the added type safety. However, I think that this last aspect of generics is overrated. I have used zillions of Collections in very large Java applications without ever finding any error related to inserting the wrong type in one. If your Collections are encapsulated (as it should be) it is fairly easy to avoid mistakes. In fact my favorites Java 1.5 features are the new Enum functionnality, annotation(s) and the very rich concurrency package. Annotation is probably the most powerfull of the bunch. It let's you add meta-data to classes that you can use in unit tests, tools (code generators, ...) or at runtime in your application (using the reflection API). The great book "Java Concurrency in practice" for example has annotations for all sorts of concurrency related properties. The simplest example is the @NotThreadSafe annotation. A tool (the excellent FindBug might eventually do this) could check and give you a warning if it looks like your using an instance from different thread. I have used annotation recently to enforce rules about data member handling in a specific class method. This aspect of the class is tested in a unit test and if someone adds a member to the class without the proper annotaion or proper handling in the method tested; the unit test will fail. This kind of mechanism is great in large teams where comments just arent srong enough. Annotations are now used by persistence framework (Hibernate, ..), aspect oriented tools (AspectJ, ...) and other tools. ..oh boy, now I'm really of topic. Sorry JS

peter_econ
peter_econ

This is atypo, thanks. What is meant is that surely you cannot write null elements.

Justin James
Justin James

I am in agreement with you on the type safety issue... I beleive that is it extremely overrated. I have to ask myself, "what programmer is sticking things into a collection but has no idea what it is... how are they going to safely use it or get it out afterwards?" To me, not verifying the type until you hit the collection you are storing it in smacls of laziness... why not catch it at the input level? J.Ja