Wednesday, February 20, 2013

Get and Put Principle in Java Generics

Introduction

It is hard to imagine modern Java development without using generics. While they look more simple and straightforward than C++ templates, it makes sense to invest some time to learn their best practices. In this post I want to talk about “Get and Put Principle” – one of the most important rules to remember when working with generics.

Basic Information

Quite often “Get and Put Principle” is defined in terms of getting values out of a data structure and putting values into it:

Use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don’t use a wildcard when you both get and put.

Let’s look at how judicious application of “Get and Put Principle” allows for creation of more flexible code. Assume you need to implement a method which gets the values from a source collection and adds them to a destination collection. It might look like this:
public static <T> void addAll(Collection<T> dst, Collection<T> src) {
    for (T element : src) {
        dst.add(element);
    }
}
Here is a sample call of this method:
Collection<Integer> src = Arrays.asList(1, 2, 3, 4);
Collection<Integer> dst = new ArrayList<Integer>();

addAll(dst, src);
At first glance addAll() method looks good, but it is not flexible enough. For example it is safe to append contents of the src collection to the dst collection typed Collection<Object>, but current implementation of the addAll() method disallows this:
Collection<Integer> src = Arrays.asList(1, 2, 3, 4);
Collection<Object> dst = new ArrayList<Object>();

addAll(dst, src); // compile-time error
To make the previous call compile without an error the dst parameter type should be modified in accordance with “Get and Put Principle”. Since we only put values into dst collection super wildcard should be used:
public static <T> void addAll(Collection<? super T> dst,
        Collection<T> src) {
    for (T element : src) {
        dst.add(element);
    }
}
This version looks better and works fine in almost any case, but it can be made even more flexible. Occasionally it might be necessary to provide explicit type parameter for generic method invocation. Let’s look at a bit contrived but illustrative example:
Collection<Integer> src = Arrays.asList(1, 2, 3, 4);
Collection<Object> dst = new ArrayList<Object>();

CollectionUtils.<Number>addAll(dst, src); // compile-time error
The example assumes that addAll() is a public static method in the CollectionUtils class. This is necessary because otherwise it is impossible to specify explicit type parameter:
<Number>addAll(dst, src); // this syntax is not allowed in Java
Alternatively you can make addAll() method a non-static member and provide explicit type parameter using the following code:
this.<Number>addAll(dst, src);
Ok, let’s break the example. In this case we refused from compiler type inference and provided an explicit type parameter for generic method invocation (<Number>). Hence src parameter of addAll() method is supposed to be of type Collection<Number>. But the actual argument is of type Collection<Integer>. Taking to account that generics are not covariant in Java (i.e. Collection<Integer> is not considered a subtype of Collection<Number> despite the fact that Integer is a subtype of Number) compiler disallows the call.

To make the previous call compile without an error the src parameter should be modified in accordance with “Get and Put Principle”. Since we only get values out of src collection extends wildcard should be used:
public static <T> void addAll(Collection<? super T> dst,
        Collection<? extends T> src) {
    for (T element : src) {
        dst.add(element);
    }
}
As you can see judicious application of “Get and Put Principle” may add considerable flexibility to your code.

Another Look

Some programmers tend to think that “Get and Put Principle” is worth considering only while working with the Collections Framework. But this is not the case. “Get and Put Principle” can also be defined in terms of method arguments and return values and applied to any generic class, not just a collection:

Use an extends wildcard when you only get return values out of a method, use a super wildcard when you only pass arguments into a method, and don’t use a wildcard when you both get and pass.

Let’s look at the following code which incorporates extends wildcard:
List<? extends Number> list = new ArrayList<Integer>();
list.add(7); // compile-time error
list.add(null);
Number value1 = list.get(0);
The first line creates a reference list and points it to an instance of ArrayList<Integer>. The second line tries to add an integer value to ArrayList<Integer> by accessing it via wildcard-typed reference list. If this call would be allowed by the compiler then the following code would also be allowed which would definitely cause problem at runtime and compromise Java type system:
List<? extends Number> list = new ArrayList<Double>();
list.add(7);
The third line compiles without an error because null literal according to JLS can be of any reference type. The fourth line also compiles and works as expected.

Now let’s look at the code which incorporates super wildcard:
List<? super Integer> list = new ArrayList<Integer>();
list.add(7);
Number value2 = list.get(0); // compile-time error
Object value3 = list.get(0);
The first line creates a reference list and points it to an instance of ArrayList<Integer>. The second line tries to add an integer value to ArrayList<Integer> by accessing it via wildcard-typed reference list. This time compiler allows the call because type parameter is guaranteed to be a super-type of Integer class; hence it is safe to pass an instance of Integer class itself as an argument to the add() method. The third line tries to retrieve the first element from the list using a reference typed via super wildcard. This call is disallowed by the compiler because it cannot guarantee the return value of get() method to be of type Number. The fourth line compiles and works as expected though.

Conclusion

Ok, that’s it. As you can see, judicious application of “Get and Put Principle” may add considerable flexibility to your code while keeping compile-time guarantees. This is one of the most important rules to remember when designing APIs involving generics.

Thanks for reading,
See you soon!

1 comment: