Saturday, November 12, 2005

Generics in Java

I've finally started trying to learn about the new Java 5 features...

Generics is one of the most important. There is a draft book by Oreilly at java.net - Generics and Collections in Java 5. And there are quite a few do's and dont's with Java generics. Definitely makes the language more complicated but it is needed too. Thought of sharing a few interesting excerpts here...

<excerpt>

Array subtyping is covariant, meaning that type S[] is considered to be a subtype of T[] whenever S is a subtype of T. Consider the following code fragment, which allocates an array of integers, assigns it to an array of numbers, and then attempts to assign a float into the array.

Integer[] ints = new Integer[] {1,2,3};
Number[] nums = ints;
nums[2] = 3.14; // array store exception
assert Arrays.toString(ints).equals("[1, 2, 3.14]"); // uh oh!

The subtyping relation for generics is invariant, meaning that type List<S> is not considered to be a subtype of List<T> except in the trivial case where S and T are identical. Here is a code fragment analogous to the one above, with lists replacing arrays.

List<Integer> ints = Arrays.asList(1,2,3);
List<Number> nums = ints; // compile-time error
nums.put(2, 3.14);
assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

</excerpt>

It is obvious that invariant behavior in generic limits is usefulness. So wildcards can be used to introduce covariant behavior.

<excerpt>

Wildcards reintroduce covariant subtyping for generics, in that type List<S> is considered to be a subtype of List<? extends T>, when S is a subtype of T. Here is a third variant of the fragment.

List<Integer> ints = Arrays.asList(1,2,3);
List<? extends Number> nums = ints;
nums.put(2, 3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

</excerpt>

And it gets even more interesting when...

<excerpt>

Wildcards also introduce contravariant subtyping for generics, in that type List<S> is considered to be a subtype of List<? super T>, when S is a supertype of T (as opposed to a subtype). Arrays do not support contravariant subtyping.

</excerpt>

So there are quite a few differences between Arrays and Generic Collections in Java and the author suggests using only Collections. The covariant and contravariant behavior is used to increase the range of Types that can be used by a Collection of type <T>.

The covariant behavior (<? extends T>) is used to get types from a colletion.

<excerpt>

Here is a method that takes a collection of numbers, converts each to a double, and sums them up.

public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums) s += num.doubleValue();
return s;
}

Since this uses extends, all of the following calls are legal.
List<Integer> ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;

List<Number> nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;

</excerpt>

So if I have a Collection of a type which extends Number, I know I can atleast safely remove type Number. This is what the author calls the Get principle.


The contravariant behavior (<? super T>) is used to put types into a colletion; which is analogous to the above behavior.


<excerpt>

Here is a method that takes a collection of numbers and an integer n, and puts the first n integers, starting from zero, into the collection.

public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}

Since this uses super, all of the following calls are legal.

List<Integer> ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");

List<Object> objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");

</excerpt>

So if I have a Collection of a type which super Integer, I know I can safely add type Integer. This is what the author calls the Put principle.

Incase a method has to both Get and Put into a same Collection, then wildcards cannot be used. Btw in this blog wherever I have used interesting you can substitute confusing. Yet with a little time and practice generic in Java should not be that tough.

A lot of this is caused because generic in Java is implemented using Erasure in which the type (<T>) is basically removed and casts are added by the compiler. So at runtime there is no info of the type. There are many more quirks caused due to Erasure... but this much is scary enough for one blog.

Could you guys compare generic in C# and C++. Also Mohn what are the new features in C#??

No comments: