What is the concept of erasure in generics in java?
It's basically the way that generics are implemented in Java via compiler trickery. The compiled generic code actually just uses java.lang.Object
wherever you talk about T
(or some other type parameter) - and there's some metadata to tell the compiler that it really is a generic type.
When you compile some code against a generic type or method, the compiler works out what you really mean (i.e. what the type argument for T
is) and verifies at compile time that you're doing the right thing, but the emitted code again just talks in terms of java.lang.Object
- the compiler generates extra casts where necessary. At execution time, a List<String>
and a List<Date>
are exactly the same; the extra type information has been erased by the compiler.
Compare this with, say, C#, where the information is retained at execution time, allowing code to contain expressions such as typeof(T)
which is the equivalent to T.class
- except that the latter is invalid. (There are further differences between .NET generics and Java generics, mind you.) Type erasure is the source of many of the "odd" warning/error messages when dealing with Java generics.
Other resources:
- Sun documentation
- Wikipedia
- Gilad Bracha's Java generics guide (PDF - highly recommended)
- Angelika Langer's Java Generics FAQ
As I understand it (being a .net guy) the jvm has no concept of generics so the compiler replaces type parameters with Object and performs all the casting for you.
This means that java generics are nothing but syntax sugar and don't offer any performance improvement for value types that require boxing/unboxing when passed by reference.
To complete the already very complete Jon Skeet's answer, you have to realize the concept of type erasure derives from a need of compatibility with previous versions of java.
From this presentation:
- Source compatibility (Nice to have...)
- Binary compatibility (Must have!)
- Migration compatibility
- Existing programs must continue to work
- Existing libraries must be able to use generic types
- Must have!
Hence:
new ArrayList<String>() => new ArrayList()
There are propositions for a greater reification. Reify being "Regard an abstract concept as real", where Language constructs should be concepts, not just syntactic sugar.
I should also mention the checkCollection method of Java6, which returns a dynamically typesafe view of the specified collection. Any attempt to insert an element of the wrong type will result in an immediate ClassCastException.
The generics mechanism in the language provides compile-time (static) type checking, but it is possible to defeat this mechanism with unchecked casts.
Usually this is not a problem, as the compiler issues warnings on all such unchecked operations.
There are, however, times when static type checking alone is not sufficient, like:
- when a collection is passed to a third-party library and it is imperative that the library code not corrupt the collection by inserting an element of the wrong type.
- a program fails with a ClassCastException, indicating that an incorrectly typed element was put into a parameterized collection. Unfortunately, the exception can occur at any time after the erroneous element is inserted, so it typically provides little or no information as to the real source of the problem.
Complementing the already-complemented Jon Skeet answer...
It has been mentioned that implementing generics through erasure leads to some annoying limitations (e.g. no new T[42]
). It has also been mentioned that the primary reason for doing things this way was backwards compatibility in the bytecode. This is also (mostly) true. The bytecode generated -target 1.5 is somewhat different from just de-sugared casting -target 1.4. Technically, it's even possible (through immense trickery) to gain access to generic type instantiations at runtime, proving that there really is something in the bytecode.
The more interesting point (which has not been raised) is that implementing generics using erasure offers quite a bit more flexibility in what the high-level type system can accomplish. A good example of this would be Scala's JVM implementation vs CLR. On the JVM, it is possible to implement higher-kinds directly due to the fact that the JVM itself imposes no restrictions on generic types (since these "types" are effectively absent). This contrasts with the CLR, which has runtime knowledge of parameter instantiations. Because of this, the CLR itself must have some concept of how generics should be used, nullifying attempts to extend the system with unanticipated rules. As a result, Scala's higher-kinds on the CLR are implemented using a weird form of erasure emulated within the compiler itself, making them not-entirely-compatible with plain-old .NET generics.
Erasure may be inconvenient when you want to do naughty things at runtime, but it does offer the most flexibility to the compiler writers. I'm guessing that's part of why it's not going away any time soon.
Just as a side-note, it is an interesting exercise to actually see what the compiler is doing when it performs erasure -- makes the whole concept a little easier to grasp. There is a special flag you can pass the compiler to output java files that have had the generics erased and casts inserted. An example:
javac -XD-printflat -d output_dir SomeFile.java
The -printflat
is the flag that gets handed off to the compiler that generates the files. (The -XD
part is what tells javac
to hand it to the executable jar that actually does the compiling rather than just javac
, but I digress...) The -d output_dir
is necessary because the compiler needs some place to put the new .java files.
This, of course, does more than just erasure; all of the automatic stuff the compiler does gets done here. For example, default constructors are also inserted, the new foreach-style for
loops are expanded to regular for
loops, etc. It is nice to see the little things that are happening automagically.