Understanding Java Generics through Erasure

by

Now that I’ve been playing with generics for nearly a year, I think I better understand how to convey what’s really going on with them in one simple example.

Erasure for Compilation

Consider the following very simple class which merely holds a reference to a character sequence generically:

    class Foo<E extends CharSequence> {
        E mE;
        Foo(E e) {
            mE = e;
        }
        E get() {
            return mE;
        }
    }

The first key to understanding this class is to understand how it looks with all the generics "erased":

    class Foo {
        CharSequence mE;
        Foo(CharSequence e) {
            mE = e;
        }
        CharSequence get() {
            return mE;
        }
    }

The erased version is the one that is compiled. Thus a client can use the class with no generics whatsoever according to the erased version.

Note that the erasure left the interface being extended by the generic E behind. So clients can assign the results of a get to a CharSequence variable. They cannot construct an instance by calling something like new Foo(new Integer(5)), because it doesn’t match the erasure.

Casting for Clients

Now what happens with clients of the class that use the generic. For instance, I might do this:

    Foo<String> foo = new Foo<String>("bar");
    String value = foo.get();

If the target of compilation is the erased class, how is this simple client able to assign the value of the getter to a string? The answer is that the compiler inserts implicit casts, so that the code above is equivalent to one with appropriate casts inserted:

    Foo<String> foo = new Foo<String>("bar");
    String value = (String) foo.get();

The guarantee is that the casts so-inserted will not cause run time cast class exceptions, because the declaration of foo as Foo<String> requires a compile-time check that all arguments match the declaration. Thus we can do this:

    new Foo(new StringBuilder());

we cannot do this:

    new Foo<String>(new StringBuilder());

It will fail the compile-time test.

Why no Instances?

The erased form of the class indicates why it’s impossible to create generic arrays. For instance, I couldn’t declare a method in Foo like this:

    E[] toArray() {
        return new E[] { mE };
    }

The problem is with the erasure:

    CharSequence[] toArray() {
        return new CharSequence[] { mE };
    }

and the corresponding client code:

    Foo<String> foo = new Foo<String>("bar");
    String[] s = foo.toArray();	

Specifically, the implicit cast causes an error:

    String[] s = (String[]) foo.toArray();	

This is because Java doesn’t let you cast a CharSequence[] to a String[].

One Response to “Understanding Java Generics through Erasure”

  1. Neeraj Singh Says:

    Thanks… it is a very good example. In fact, we were going through Item#25 of Effective Java 2nd Edition and we came across this and had a discussion. This helps in clear understanding..

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s