Saturday, 26 March 2011

Java Generics Tutorial - Part I - Basics

Generics is a Java feature that was introduced with Java SE 5.0 and, few years after its release, I swear that every Java programmer out there not only heard about it, but used it. There are plenty of both free and commercial resources about Java generics and the best sources I used are:
Despite the wealth of information out there, sometimes it seems to me that many developers still don't understand the meaning and the implications of Java generics. That's why I'm trying to summarize the basic information developers need about generics in the simplest possible way.

This blog post is made up of the following parts:

The Motivation for Generics

The simplest way to think about Java generics is thinking about a sort of a syntactic sugar that might spare you some casting operation:

List<Apple> box = ...;
Apple apple = box.get(0);

The previous code is self-speaking: box is a reference to a List of objects of type Apple. The get method returns an Apple instance an no casting is required. Without generics, this code would have been:

List box = ...;
Apple apple = (Apple) box.get(0);

Needless to say, the main advantage of generics is having the compiler keep track of types parameters, perform the type checks and the casting operations: the compiler guarantees that the casts will never fail.

Instead of relying on the programmer to keep track of object types and performing casts, which could lead to failures at runtime difficult to debug and solve, the compiler can now help the programmer enforce a greater number of type checks and detect more failures at compile time.

The Generics Facility


The generics facility introduced the concept of type variable. A type variable, according to the Java Language Specification, is an unqualified identifier introduced by:
  • Generic class declarations.
  • Generic interface declarations.
  • Generic method declarations.
  • Generic constructor declarations.

Generic Classes and Interfaces

A class or an interface is generic if it has one or more type variable. Type variable are delimited by angle brackets and follow the class (or the interface) name:

public interface List<T> extends Collection<T> {
  ...
}

Roughly speaking, type variables act as parameters and provide the information the compiler needs to make its checks.

Many classes in the Java library, such as the entire Collections Framework, were modified to be generic. The List interface we've used in the first code snippet, for example, is now a generic class. In that snippet, box was a reference to a List<Apple> object, an instance of a class implementing the List interface with one type variable: Apple. The type variable is the parameter that the compiler uses when automatically casting the result of the get method to an Apple reference.

In fact, the new generic signature or the get method of the interface List is:

T get(int index);

The method get returns indeed an object of type T, where T is the type variable specified in the List<T> declaration.

Generic Methods and Constructors


Pretty much the same way, methods and constructors can be generic if they declare one or more type variables.

public static <T> T getFirst(List<T> list)

This method will accept a reference to a List<T> and will return an object of type T.

Examples

You can take advantage of generics in both your own classes or the generic Java library classes.

Type Safety When Writing...

In the following code snippet, for example, we create an instance List<String> of populate it with some data:

List<String> str = new ArrayList<String>();
str.add("Hello ");
str.add("World.");

If we tried to put some other kind of object into the List<String>, the compiler would raise an error:

str.add(1); // won't compile

... and When Reading

If we pass the List<String> reference around, we're always guaranteed to retrieve a String object from it:

String myString = str.get(0);

Iterating

Many classes in the library, such as Iterator<T>, have been enhanced and made generic. The iterator() method of the interface List<T> now returns an Iterator<T> that can be readily used without casting the objects it returns via its T next() method.


for (Iterator<String> iter = str.iterator(); iter.hasNext();) {
  String s = iter.next();
  System.out.print(s);
}

Using foreach

The for each syntax takes advantage of generics, too. The previous code snippet could be written as:

for (String s: str) {
  System.out.print(s);
}

that is even easier to read and maintain.

Autoboxing and Autounboxing

The autoboxing/autounboxing features of the Java language are automatically used when dealing with generics, as shown in this code snippet:

List<Integer> ints = new ArrayList<Integer>();
ints.add(0);
ints.add(1);
      
int sum = 0;
for (int i : ints) {
  sum += i;
}

Be aware, however, that boxing and unboxing come with a performance penalty so the usual caveats and warnings apply.

Next Steps

In the next part of this post, we will see subtyping relations of generic types.

No comments:

Post a Comment