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     PermaLink

6 Responses to “Java generics quirks”

  1. Jesse Wilson Says:

    “objArr.add” is fairly suspicious… a typo?

  2. Jesper de Jong Says:

    Jesse, you’re right, I’ve corrected the example above. Thanks.

  3. Eric Blische Says:

    Thanks for the explanation, I still don’t like it though (smile).
    At least I have a grasp on what’s going on behid the scenes, now !

    - E

  4. Ramachandran Says:

    I want to store the integer values into ArrayList within the looping statement.
    Please guide me …

  5. David Putnam Says:

    Jasper I enjoyed reading your article, but I did not see any reference to the Array’s Component Type attribute. It would seem that as long as Class can be referenced as a Class, type erasure only means that if the object is an array there is a different check that must be made, specifically to the array component type, instead of the object class.

    David

  6. Jesper de Jong Says:

    David, can you explain in more detail what you mean?

    The main point in the article is this: Normally, when you assign a value to an element of an array, an array store check is done. If you put something in the array that “doesn’t fit” (because it’s the wrong type), an ArrayStoreException is thrown.

    Now generics in Java work with type erasure. Type erasure throws away part of the runtime type information. Because of that, the array store check cannot be done properly. The information that the array store check is simply not available at runtime, because type erasure has discarded a part of the necessary information at compile time.

    The conclusion is that the array store check cannot work correctly for arrays of generic types. The engineers at Sun didn’t really solve the problem, they just avoided it by making arrays of generic types illegal…

Leave a Reply


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