Java generics quirks
Monday 16 January 2006 @ 12:31 pm
Filed under:

One of the major new features of Java 5.0 is generics. The main purpose of generics is to provide you with a way to make type safe collections, for example, you can specify that your List contains only String objects. The obvious advantages are that the compiler can check at compile time that nothing else than String objects are stored in the List and that you can eliminate a lot of type casts from your source code.

// Old style, not type safe
List names = new ArrayList();
names.add("Jesper");
names.add(new Integer(123)); // not what you meant, but no error or warning

// Java 5.0, using generics
List<String> names = new ArrayList<String>();
names.add("Jesper");
names.add(new Integer(123)); // compiler error

That seems simple enough, but if you study generics in more detail, you will discover that there are some strange limitations that don’t look obvious at first sight.

For example, here is one limitation of generics I would like to highlight: You cannot create an array whose component type is a concrete parameterized type. An example to explain what this means:

// An array of a list of strings. This is illegal, but why?
List<String>[] data = new ArrayList<String>[10];

At first sight, this looks perfectly reasonable. But if you try to compile this, you get an error message, and it isn’t very helpful either:

Jesper.java:6: generic array creation
        List<String>[] data = new ArrayList<String>[10];
                              ^
1 error

The compiler just tells you that you can’t do this, but it doesn’t give a hint why. To understand why this is illegal, we have to know a few things about arrays and generics in Java.

First of all, arrays are covariant, which means that an array of supertype references is a supertype of an array of subtype references. For example, Object[] is a supertype of String[], and a string array can be accessed through a reference variable of type Object[].

Object[] example = new String[10]; // ok
example[0] = "Hello World!";

Arrays also carry runtime type information about their component type. At runtime, when you assign a value to an array element, an array store check is performed to check that you’re not storing a value in the array that’s not compatible with its component type. If the check fails, an ArrayStoreException is thrown.

Object[] example = new String[10];
example[0] = new Long(123); // compiles, but throws ArrayStoreException at runtime

That still doesn’t explain why arrays of concrete parameterized types are illegal. There’s one more thing we have to understand, and that is the process of type erasure.

When you compile Java source code in which generic types are used, the compiler does the extra type checks and then translates the code into plain old Java code without generics. If you would decompile the byte code, you would see that the generics have disappeared. So for example List<String> and List<Integer> would both look like the plain old List in the byte code - the information about the generics has been lost by the process of type erasure. The following example illustrates this:

List<String> names = new ArrayList<String>();
List<Integer> values = new ArrayList<Integer>();

System.out.println("Runtime type of 'names': " + names.getClass().getName());
System.out.println("Runtime type of 'values': " + values.getClass().getName());

The output of this piece of code is:

Runtime type of 'names': java.util.ArrayList
Runtime type of 'values': java.util.ArrayList

As you see, both look like plain old ArrayList at runtime. I won’t go into the details here of how and why the compiler handles generics using type erasure.

So here comes the catch. The combination of how arrays work (covariance and runtime type information about the component type) and type erasure for generic types creates a problem when you want to create an array of a concrete parameterized type.

Because of type erasure, there is no exact type information available for the generic type at runtime, so that the array store check doesn’t work, and an unacceptable situation occurs. Here’s an example.

List<String>[] data = new ArrayList<String>[10]; // illegal, but assume that this would be ok

Object[] objArr = data;
objArr[0] = new ArrayList<Integer>(); // should fail, but would succeed

List<String> firstList = data[0]; // would fail with ClassCastException, but there's no cast!

Because of type erasure, List<String> looks like a plain List and ArrayList<Integer> looks like a plain ArrayList at runtime. The array store check would succeed, because ArrayList is a subtype of List. Ofcourse we now end up in an unacceptable situation - the first element of the array now contains an ArrayList<Integer>, which shouldn’t be possible.

To avoid this problem, the Java language engineers at Sun simply decided to make arrays of concrete parameterized types illegal.

Isn’t it ugly that a construct that looks perfectly reasonable (you can make an array of any type, so why not of a concrete parameterized type?) is made illegal essentially because of the way the compiler treats generics? In other words, the language has a hole now because of the implementation details of the compiler.

This is just one of the quirks that you find when you look at generics in depth. You can find a lot of detailed information about generics in Angelika Langer’s Java Generics FAQ.

— By Jesper de Jong   Comments (4)   PermaLink

Menu


Blog Categories

Browse by Date
January 2006
M T W T F S S
 1
2345678
9101112131415
16171819202122
23242526272829
3031EC

Upcoming Events

Monthly Archives

Recent Comments

Links


XML Feeds Option


Get Firefox  Powered by WordPress

code validations
Valid RSS 2.0  Valid Atom 0.3
Valid W3C XHTML 1.0  Valid W3C CSS