Making the Most of Java 5: Exploring Generics

Tagged:  

Erasure aside, the addition of generics to Java (see intro here) brings added dimension to using types in the language. The most obvious application of generics is the ability to declare homogeneous Collection types, such as Collection<String>, but there are less obvious ones as well. I'll explore some of these in this article.

Type Inference

But first, a little diversion. Generics can be used both at the class level and at the method level in Java. Let's suppose we have a generic method like so:

public <T> T getSomething() { ... }

What happens if we do an assignment to a String?

String s = getSomething();

It compiles just fine. The same is true of the following:

Integer i = getSomething();

What is going on here? As you may have guessed, generic methods have an ability to do type inference, that is, they are able to figure out the type from the context of its use in an assignment expression. Quite a new wrinkle for an explicitly-typed language like Java.

If you define a generic type for a parameter to the method getSomething() that also uses <T>, the type defined by the parameter takes "precedence" over assignment inference.

Type-Inferred Factory Method

As Joshua Bloch pointed out in his "Effective Java Reloaded" presentation at JavaOne 2007, you can use this type inference to simplify the creation of complex generic types using a static factory method. Adding a method to the java.util.HashMap class like

public static <K,V> HashMap<K,V> newInstance() {
     return new HashMap<K,V>();
}

simplifies the creation of a complex HashMap object from

Map<String,List<Integer>> map = new HashMap<String,List<Integer>>();

to a much easier-to-read form like

Map<String,List<Integer>> map = HashMap.newInstance();

Type inference makes this possible.

Type-Safe Factory Method

Often factory methods will have a very general applicability, and so in earlier versions of Java, were forced to return an Object which was expected to be cast to the correct type. A good example of this is the java.lang.reflect.Proxy class which has the method:

public static Object newProxyInstance(ClassLoader loader,
     Class<?>[] interfaces,
     InvocationHandler h) 

Assuming that we only want to use the current ClassLoader and we are proxying using only a single interface, we can genericize this method to the following:

public static <T> T newProxyInstance(Class<T> interface, 
     InvocationHandler h)

(One could also genericize the InvocationHandler class with the generic type <T>, as well, so that the first argument of its invoke method was the correct type). Here interface argument is also functioning as a "type token", providing a guarantee that the factory method will return an instance of the correct type and the implementer with the means to do so (via the cast()). Since the type of the Class parameter passed in determines the type of <T>, it acts as a kind of "hook" to determine the type of the return value. Thus this method will not need to have its return value explicitly cast, and will never throw a ClassCastException.

Another great example of this technique are the various bind() methods for the Binder class in the Google Guice library. The type passed into the bind() method is "hooked" via a generic type to the type passed into the various chained to(), toProvider(), toInstance(), and annotatedWith() methods. Hence a "line of configuration" in a Google Guice Module that would define the dependency injection for a Collection interface would look like:

binder.bind(Collection.class).to(ArrayList.class);

Thanks to the use of a generic type, the parameter to the method to() is guaranteed to be of an assignable type to that of the method bind().

Guarantee of Behavior

A little-known part of the Generics specification is the ability to define conjunctive types, that is, multiple types separated by an '&' operator, when defining a generic bounded type. You can use this to make sure that a parameter to a method implements specific interfaces. For example, let's say that you have a method that takes an implementation of a certain interface MyInterface.

public void doSomething(MyInterface onObject) { ... }

Let's also say that you want the passed-in parameter to also implement java.lang.Comparable and java.io.Serializable so that you could sort and serialize the parameter in the method. How would you do this? Well, you could put guard code in your method and throw an IllegalArgumentException--and document this well--but compile-time errors are much preferable to runtime ones. This is where conjunctive generic types come to the rescue...you can change this method to the following:

public <T extends MyInterface & Comparable<T> & Serializable> 
     void doSomething(T onObject)

Now the caller of the method will get a compile error if they try to pass in an implementation of MyInterface that does not also implement the other two interfaces.

Conclusion

Despite some annoyances to the generics implementation in Java (read: erasure), Generics do bring an extra layer to the type safety of the language that can be used in new and creative ways. Once you get past the gotchas, they make your Java programs safer.

[...] Making the Most of Java 5: Exploring Generics By Brennan Spies Erasure aside, the addition of generics to Java (see intro here) brings added dimension to using types in the language. The most obvious application of generics is the ability to declare homogeneous Collection types, … Ajaxonomy - The Study of Ajax… - http://www.ajaxonomy.com [...]

[...] Spies explores some of the uses of Generic in Java on his blog [...]

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <div> <blockquote> <object> <embed> <img> <param>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Copy the characters (respecting upper/lower case) from the image.