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[]
.
October 23, 2009 at 10:49 pm |
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..