|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
HOME | SEMINARS | TALKS | ARTICLES | BOOKS | LINKS | IOSTREAMS | GENERICS | ABOUT | NEWSLETTER | CONTACT | SITEMAP | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Java Generics FAQs - Under The Hood Of The Compiler
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
This is a collection of answers to frequently asked questions
(FAQs) about Java Generics, a new language feature added to the Java programming
language in version 5.0 of the Java Standard Edition (J2SE 5.0).
If you want to provide feedback or have any questions regarding Java
generics, to which you cannot find an answer in this document, feel free
to send me
EMAIL
or use the
GENERICS FAQ
form.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Technicalities - Under The Hood Of The Compiler© Copyright 2003-2007 by Angelika Langer. All Rights Reserved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| A warning by which the compiler indicates that it cannot ensure type safety. | |
|
The term "unchecked" warning is misleading. It does
not mean that the warning is unchecked in any way. The term "unchecked"
refers to the fact that the compiler and the runtime system do not have
enough type information to perform all type checks that would be necessary
to ensure type safety. In this sense, certain operations are "unchecked".
The most common source of "unchecked" warnings is the use of raw types. "unchecked" warnings are issued when an object is accessed through a raw type variable, because the raw type does not provide enough type information to perform all necessary type checks. Example (of unchecked warning in conjunction with raw types): TreeSet se t = new TreeSet();When the add method is invoked the compiler does not know whether it is safe to add a String object to the collection. If the TreeSet is a collection that contains String s (or a supertype thereof), then it would be safe. But from the type information provided by the raw type TreeSet the compiler cannot tell. Hence the call is potentially unsafe and an "unchecked" warning is issued. "unchecked" warnings are also reported when the compiler finds a cast whose target type is either a parameterized type or a type parameter. Example (of an unchecked warning in conjunction with a cast to a parameterized type or type variable): A cast whose target type is either a (concrete or bounded wildcard) parameterized type or a type parameter is unsafe, if a dynamic type check at runtime is involved. At runtime, only the type erasure is available, not the exact static type that is visible in the source code. As a result, the runtime part of the cast is performed based on the type erasure, not on the exact static type. In the example, the cast to Wrapper<T> would check whether the object returned from super.clone is a Wrapper , not whether it is a wrapper with a particular type of members. Similarly, the casts to the type parameter T are cast to type Object at runtime, and probably optimized away altogether. Due to type erasure, the runtime system is unable to perform more useful type checks at runtime. In a way, the source code is misleading, because it suggests that a cast to the respective target type is performed, while in fact the dynamic part of the cast only checks against the type erasure of the target type. The "unchecked" warning is issued to draw the programmer's attention to this mismatch between the static and dynamic aspect of the cast. |
|
| LINK TO THIS | #FAQ001 |
| REFERENCES |
What
does type-safety mean?
How can I disable or enable unchecked warnings? What is the raw type? Can I use a raw type like any other type? Can I cast to a parameterized type? Can I cast to the type that the type parameter stands for? |
How
can I disable or enable "unchecked" warnings?
| Via the compiler options -Xlint:unchecked and -Xlint:-unchecked and via the standard annotation @SuppressWarnings("unchecked") . | |
|
The compiler option
-Xlint:-unchecked
disables
all
unchecked warnings that would occur in a compilation.
The annotation @SuppressWarnings("unchecked") suppresses all warnings for the annotated part of the program. Note, in the first release of Java 5.0 the SuppressWarnings annotation is not yet supported. |
|
| LINK TO THIS | #FAQ002 |
| REFERENCES |
What
is the -Xlint:unchecked compiler option?
What is the SuppressWarnings annotation? |
| The compiler option -Xlint:unchecked enables "unchecked" warnings, the option -Xlint:-unchecked disables all unchecked warnings. | |
"unchecked" warnings are by default disabled. If
you compile a program with no particular compiler options then the compiler
will not report any "unchecked" warnings. If the compiler finds source
code for which it would want to report an "unchecked" warning it only gives
a general hint. You will find the following note at the end of the
list of all other errors and warnings:
Note: util/Wrapper.java uses unchecked or unsafe operations.If you want to see the "unchecked" warnings you must start the compiler with the -Xlint:unchecked option. Example (of globally enabling unchecked warnings): javac -Xlint:unchecked util/Wrapper.javaThe option -Xlint:unchecked en ables the "unchecked" warnings. The "unchecked" warnings are also enabled when you use the -Xlint:all option. The option -Xlint:-unchecked disables the "unchecked" warnings. This is useful to suppress all "unchecked" warnings, while other types of warnings remain enabled. Example (of globally disabling unchecked warnings): javac -g -source 1.5 -Xlint:all -Xlint:-unchecked util/Wrapper.javaIn this example, using -Xlint:all all warnings (such as "unchecked", "deprecated", "fallthrough", etc.) are enabled and subsequently the "unchecked" warnings are disabled using -Xlint:-unchecked . As a result all warnings except "unchecked" warnings will be reported. |
|
| LINK TO THIS | #FAQ003 |
| REFERENCES |
What
is an "unchecked" warning?
What is the SuppressWarnings annotation? |
| A standard annotation that suppresses warnings for the annotated part of the program. | |
|
The compiler supports a number of standard annotations
(see package
java.lang.annotation
).
Among them is the
SuppressWarnings
annotation. It contains a list
of warning labels. If a definition in the source code is annotated
with the
SuppressWarnings
annotation, then all warnings, whose
labels appear in the annotation's list of warning labels, are suppressed
for the annotated definition or any of its parts.
The SuppressWarnings annotation can be used to suppress any type of labelled warning. In particular we can use the annotation to suppress "unchecked" warnings. Example (of suppressing unchecked warnings): This example would usually raise 2 "unchecked" warnings. Since we annotated the entire class, all unchecked warnings raised anywhere in the class implementation are suppressed. We can suppress several types of annotations at a time. In this case we must specify a list of warning labels. Example (of suppressing several types of warnings): This example would usually raise 2 warnings when the call to method add is compiled: warning: [deprecation] Date(int,int,int) in java.util.Date has been deprecatedThe annotation preceding the enclosing method suppresses all unchecked and deprecation warnings anywhere in the method implementation. Annotations can not be attached to statements, expressions, or blocks, only to program entities with a definition like types, variables, etc. Example (of illegal placement of annotation): public static void someMethod() {Annotations can be attached to the definition of packages, classes, interfaces, fields, methods, parameters, constructors, local variables, enum types, enum constants, and annotation types. An annotated package declaration must go into a file named package-info.java in the directory that represents the package. Note, in release of Java 5.0 the SuppressWarnings annotation is not yet supported by all compilers. Sun's compiler will support in in release 6.0. |
|
| LINK TO THIS | #FAQ004 |
| REFERENCES |
What
is an "unchecked" warning?
How can I disable or enable unchecked warnings? |
| By using an unbounded wildcard parmeterized type as target type of a cast expression. | |
|
Occasionally, we would like to cast to a parameterized
type, just to discover that the compiler flags it with an "unchecked" warning.
As we are interested in warning-free compilation of our source code, we
would like to avoid this warning. Use of an unbounded wildcard parameterized
type instead of a concrete or a bounded wildcard parameterized type would
help avoid the warning.
A typical example is the implementation of methods such as the equals method, that take Object reference and where a cast down to the actual type must be performed. Example (not recommended): class Wrapper<T> {When we replace the cast to Wrapper<T> by a cast to Wrapper<?> the warning disappears, because unbounded wildcard parameterized types are permitted as target type of a cast without any warnings. Example (implementation of equals ): class Wrapper<T> {Note, this technique works in this example only because we need no write access to the fields of the object refered to through the wildcard parameterized type and we need not invoke any methods. Remember, use of the object that a wildcard reference variable refers to is restricted. In other situations use of a wildcard parameterized type might not be a viable solution, because full access to the referenced object is needed. |
|
| LINK TO THIS | #FAQ005 |
| REFERENCES |
Can
I cast to a parameterized type?
What is an "unchecked" warning? How can I disable or enable unchecked warnings? How do I best implement the equals method of a generic type? |
| Almost. | |
|
"Unchecked" warnings stem either from using generic types
in their raw form or from casts whose target type is a type parameter or
a concrete or bounded wildcard parameterized type. If you refrain from
both using raw types and the critical casts you can theoretically eliminate
all "unchecked" warnings. Whether this is doable in practice depends
on the circumstances.
Raw types. When source code is compiled for use in Java 5.0 that was developed before Java 5.0 and uses classes that are generic in Java 5.0, then "unchecked" warnings are inevitable. For instance, if "legacy" code uses types such as List , which used to be a regular (non-generic) types before Java 5.0, but are generic in Java 5.0, all these uses of List are considered uses of a raw type in Java 5.0. Use of the raw types will lead to "unchecked" warnings. If you want to eliminate the "unchecked" warnings you must re-engineer the "legacy" code and replace all raw uses of List with appropriate instantiations of List such as List<String> , List<Object> , List<?> , etc. All "unchecked" warnings can be eliminated this way. In source code developed for Java 5.0 you can prevent "unchecked" warnings in the first place by never using raw types. Always provide type arguments when you use a generic type. There are no situations in which you are forced to use a raw type. In case of doubt, when you feel you have no idea which type argument would be appropriate, try the unbounded wildcard " ? ". In essence, "unchecked" warnings due to use of raw types can be eliminated if you have access to legacy code and are willing to re-engineer it. Casts. "Unchecked" warnings as a result of cast expressions can be eliminated by eliminating the offensive casts. Eliminating such casts is almost always possible. There are, however, a few situations where a cast to a type parameter or a concrete or bounded wildcard parameterized type cannot be avoided. These are typically situations where a method returns a supertype reference to an object of a more specific type. The classic example is the clone method; it returns an Object reference to an object of the type on which it was invoked. In order to recover the returned object's actual type a cast in necessary. If the cloned object is of a parameterized type, then the target type of the cast is an instantiation of that parameterized type, and an "unchecked" warning is inevitable. The clone method is just one example that leads to unavoidable "unchecked" warnings. Invocation of methods via reflection has similar effects because the return value of a reflectively invoked method is returned via an Object reference. It is likely that you will find further examples of unavoidable "unchecked" casts in practice. For a detailed discussion of an example see #FAQ502 , which explains the implementation of a clone method for a generic class.
In sum, there are situations in which you cannot eliminate "unchecked"
warnings due to a cast expression.
|
|
| LINK TO THIS | #FAQ006 |
| REFERENCES |
What
is an "unchecked" warning?
How do I best implement the clone method of a generic type? |
| Because the compiler performs all type checks based on the type erasure when you use a raw type. | |
|
Usually the compiler issues an "unchecked" warning in order
to alert you to some type-safety hazard that the compiler cannot prevent
because there is not enough type information available. One of these situations
is the invocation of methods of a raw type.
Example (of unchecked warning in conjunction with raw types): class TreeSet<E> {When the add method is invoked the compiler does not know whether it is safe to add a String object to the collection because the raw type TreeSet does not provide any information regarding the type of the contained elements. Curiously, an unchecked warning is also issued in situations where there is enough type information available.
Example (of a spurious unchecked warning in conjunction with raw types):
class
SomeType<T> {
In this example, there is no type information missing. The
getList
method is declared to return a
List<String>
and this is so even in the raw type because the method does not depend
on the enclosing class's type parameter. Yet the compiler complains.
public List<String> getList() { ... } } SomeType raw = new SomeType (); List<String> listString = raw.getList(); // unchecked warning warning: [unchecked] unchecked conversion found : List required: List<String> List<String> listString = raw.getList(); ^ The reason is that the compiler computes the type erasure of a generic type when it finds an occurrence of the raw type in the source code. Type erasure does not only elide all occurances of the type parameter T , but also elides the type argument of the getList method's return type. After type erasure, the getList method returns just a List and no longer a List<String> . All subsequent type checks are performed based on the type erasure; hence the "unchecked" warning.
The "unchecked" warning can easily be avoided by refraining from use
of the raw type.
SomeType
is a generic type and should always be used with a type argument. In general,
the use of raw types will inevitably result in "unchecked" warnings; some
of the warnings may be spurious, but most of them are justified.
Note, that no spurious warning is issued when the method in question is a static method.
Example (of invoking a static method of a raw type):
class
SomeType<T> {
public static List<String> getList() { ... } } SomeType raw = new SomeType (); List<String> listString = raw.getList(); // fine |
|
| LINK TO THIS | #FAQ007 |
| REFERENCES |
What
is an "unchecked" warning?
Should I prefer parameterized types over raw types? Why shouldn't I mix parameterized and raw types, if I feel like it? |
| A situation where a variable of a parameterized type refers to an object that is not of that parameterized type. | |
|
It can happen that a variable of a parameterized type such
as
List<String>
refers
to an object that is not of that parameterized type.
Example (of heap pollution):
List
ln = new ArrayList<Number>();
List<String> ls =ln; // unchecked warning String s = ls.get(0); // ClassCastException
|
|
| LINK TO THIS | #FAQ050 |
| REFERENCES |
What
is an "unchecked" warning?
When does heap pollution occur? |
When
does heap pollution occur?
| As a result of mixing raw and parameterized type, unwise casting, and separate compilation. | |
Heap pollution occurs in three situations:
Raw Types Heap pollution can occur when raw types and parameterized types are mixed and a raw type variable is assigned to a parameterized type variable. Note, that heap pollution does not necessarily occur, even if the compiler issues an unchecked warning.
Example (of mixing raw and parameterized types):
List
ln = new ArrayList<Number>();
List ls = new LinkedList<String>();
List<String> list;
Mixing raw and parameterized types should be avoided, if possible. It cannot be avoided when non-generic legacy code is combined with modern generic code. But otherwise, the mix is bad programming style.
Unchecked Casts Unwise casting can lead to all kinds of unhealthy situations. In particular, in can lead to heap pollution. Example (of cast to parameterized type polluting the heap):
List<?
extends Number> ln = new ArrayList<Long>();
The
compiler permits the two casts in the example above, because
List<?
extends Number>
is a supertype of the types
List<Short>
and
List<Long>
. The
casts are similar to casts from supertype
Object
to subtype
Short
or
Long
.
The key difference is that the correctness of a cast to a non-parameterized
type can be ensured at runtime and will promptly lead to
ClassCastException
,
while a cast to a parameterized type cannot be ensured at runtime because
of type erasure and might results in heap pollution.
List<Short> ls = (List<Short>) ln; // unchecked warning + heap pollution List<Long> ll = (List<Long>) ln; // unchecked warning + NO heap pollution
Casts with a parameterized target type can lead to heap pollution, and so do casts to type variables.
Example (of cast to type variable polluting the heap):
<S,T>
S convert(T arg) {
return (S)arg; // unchecked warning } Number n = convert(new Long(5L)); // fine String s = convert(new Long(5L)); // ClassCastException In this example we do not cast to a parameterized type, but a type variable S . The compiler permits the cast because the cast could succeed, but there is no way to ensure success of the cast at runtime. Casts, whose target type is a parameterized type or a type variable, should be avoided, if possible. Separate Compilation Another situation, in which heap pollution can occur is separate compilation of translation units.
Example (initial implementation):
file
FileCrawler.java
:
final
class FileCrawler {
... public List<String> getFileNames() { List<String> list = new LinkedList<String>(); ... return list; } }
file
Test
.
java
:
final
class Test {
public static void main(String[] args) { FileCrawler crawler = new FileCrawler("http:\\www.goofy.com"); List<String> files = crawler.getFileNames(); System.out.println(files.get(0)); } }
Example (after modification and co-compilation):
file
FileCrawler.java
:
final
class FileCrawler {
... public List< StringBuilder > getFileNames() { List< StringBuilder > list = new LinkedList< StringBuilder >(); ... return list; } }
file
Test
.
java
:
final
class Test {
When we co-compile both translation units, the compiler would report an
error in the unmodified file
Test.java
,
because the return type of the
getNames()
method does no longer match the expected type
List<String>
.
public static void main(String[] args) { FileCrawler crawler = new FileCrawler("http:\\www.goofy.com"); List<String> files = crawler.getFileNames(); // error System.out.println(files.get(0)); } }
If we compiler separately, that is, only compile the modified file Test.java , then no error would be reported. This is because the class, in which the error occurs, has not been re-compiled. When the program is executed, a ClassCastException will occur.
Example (after modification and separate compilation):
file
FileCrawler.java
:
final
class FileCrawler {
... public List< StringBuilder > getFileNames() { List< StringBuilder > list = new LinkedList< StringBuilder >(); ... return list; } }
file
Test
.
java
:
final
class Test {
public static void main(String[] args) { FileCrawler crawler = new FileCrawler("http:\\www.goofy.com"); List<String> files = crawler.getFileNames(); // fine System.out.println(files.get(0)); // ClassCastException } } This is another example of heap pollution. The compiler, since it does not see the entire program, but only a part of it, cannot detect the error. Co-compilation avoids this problem and enables the compiler to detect and report the error. Separate compilation in general is hazardous, independently of generics. If you provide a method that first returns a String and later you change it to return a StringBuilder , without re-compiling all parts of the program that use the method, you end up in a similarly disastrous situation. The crux is the incompatible change of the modified method. Either you can make sure that the modified part is co-compiled with all parts that use it or you must not introduce any incompatible changes such as changes in semantics of types or signatures of methods. |
|
| LINK TO THIS | #FAQ051 |
| REFERENCES |
What
is heap pollution?
What is an "unchecked" warning? How can I avoid "unchecked cast" warnings? Is it possible to eliminate all "unchecked" warnings? |
| By creating one unique byte code representation of each generic type (or method) and mapping all instantiations of the generic type (or method) to this unique representation. | |
|
The Java
compiler is responsible for translating Java source code that contains
definitions and usages of generic types and methods into Java byte code
that the virtual machine can interpret. How does that translation work?
A compiler that must translate a generic type or method (in any language, not just Java) has in principle two choices: Code sharing. The compiler generates code for only one representation of a generic type or method and maps all the instantiations of the generic type or method to the unique representation, performing type checks and type conversions where needed.
Code specialization is the approach that C++ takes for its templates:
Code specialization is particularly wasteful in cases where the elements in a collection are references (or pointers), because all references (or pointers) are of the same size and internally have the same representation. There is no need for generation of mostly identical code for a list of references to integers and a list of references to strings. Both lists could internally be represented by a list of references to any type of object. The compiler just has to add a couple of casts whenever these references are passed in and out of the generic type or method. Since in Java most types are reference types, it deems natural that Java chooses code sharing as its technique for translation of generic types and methods. The Java compiler applies the code sharing technique and creates one unique byte code representation of each generic type (or method). The various instantiations of the generic type (or method) are mapped onto this unique representation by a technique that is called type erasure . |
|
| LINK TO THIS | #FAQ100 |
| REFERENCES | What is type erasure? |
| A process that maps a parameterized type (or method) to its unique byte code representation by eliding type parameters and arguments. | |
|
The compiler generates only one byte code representation
of a generic type or method and maps all the instantiations of the generic
type or method to the unique representation. This mapping is performed
by type erasure. The essence of type erasure is the removal of all
information that is related to type parameters and type arguments. In addition,
the compiler adds type checks and type conversions where needed and inserts
synthetic bridge methods if necessary. It is important to understand type
erasure because certain effects related to Java generics are difficult
to understand without a proper understanding of the translation process.
The type erasure process can be imagined as a translation from generic Java source code back into regular Java code. In reality the compiler is more efficient and translates directly to Java byte code. But the byte code created is equivalent to the non-generic Java code you will be seeing in the subsequent examples. The steps performed during type erasure include:
Eliding type parameters.
Eliding type arguments.
Example (before type erasure): interface Comparable <A> {Type parameters are green and type arguments are blue . During type erasure the type arguments are discarded and the type paramters are replaced by their leftmost bound. Example (after type erasure): interface Comparable {The generic Comparable interface is translated to a non-generic interface and the unbounded type parameter A is replaced by type Object . The NumericValue class implements the non-generic Comparable interface after type erasure, and the compiler adds a so-called bridge method . The bridge method is needed so that class NumericValue remains a class that implements the Comparable interface after type erasure. The generic method max is translated to a non-generic method and the bounded type parameter A is replaced by its leftmost bound, namely Comparable . The parameterized interface Iterator<A> is translated to the raw type Iterator and the compiler adds a cast whenever an element is retrieved from the raw type Iterator .
The uses of the parameterized type
LinkedList<NumericValue>
and the generic
max
method in the
main
method are translated
to uses of the non-generic type and method and, again, the compiler must
add a cast.
|
|
| LINK TO THIS | #FAQ101 |
| REFERENCES |
What
is a bridge method?
Why does the compiler add casts when it translates generics? How does type erasure work when a type parameter has several bounds? |
| Representing type parameters and arguments of generic types and methods at runtime. Reification is the opposite of type erasure . | |
|
In Java, type parameters and type arguments are elided
when the compiler performs type erasure. A side effect of type erasure
is that the virtual machine has no information regarding type parameters
and type arguments. The JVM cannot tell the difference between a
List<String>
and a
List<Date>
.
In other languages, like for instance C#, type parameters and type arguments of generics types and methods do have a runtime representation. This representation allows the runtime system to perform certain checks and operations based on type arguments. In such a language the runtime system can tell the difference between a List<String> and a List<Date> . |
|
| LINK TO THIS | #FAQ101A |
| REFERENCES |
What
is type erasure?
What is a reifiable type? |
| A synthetic method that the compiler generates in the course of type erasure. It is sometimes needed when a type extends or implements a parameterized class or interface. | |
|
The compiler insert bridge methods in subtypes of parameterized
supertypes to ensure that subtyping works as expected.
Example (before type erasure): interface Comparable <A> {In the example, class NumericValue implements interface Comparable<NumericValue> and must therefore override the superinterface’s compareTo method. The method takes a NumericValue as an argument. In the process of type erasure, the compiler translates the parameterized Comparable<A> interface to its type erased counterpart Comparable . The type erasure changes the signature of the interface's compareTo method. After type erasure the method takes an Object as an argument. Example (after type erasure): interface Comparable {After this translation, method NumericValue.compareTo(NumericValue) is no longer an implementation of the interface's compareTo method. The type erased Comparable interface requires a compareTo method with argument type Object , not NumericValue . This is a side effect of type erasure: the two methods (in the interface and the implementing class) have identical signatures before type erasure and different signatures after type erasure.
In order to achieve that class
NumericValue
remains a class
that correctly implements the
Comparable
interface, the compiler
adds a bridge method to the class. The bridge method has the same
signature as the interface’s method after type erasure, because
that's the method that must be implemented. The bridge method delegates
to the orignal methods in the implementing class.
The existence of the bridge method does not mean that objects of arbitrary types can be passed as arguments to the compareTo method in NumericValue . The bridge method is an implementation detail and the compiler makes sure that it normally cannot be invoked. Example (illegal attempt to invoke bridge method): NumericValue value = new NumericValue((byte)0);The compiler does not invoke the bridge method when an object of a type other than NumericValue is passed to the compareTo method. Instead it rejects the call with an error message, saying that the compareTo method expects a NumericValue as an argument and other types of arguments are not permitted. You can, however, invoke the synthetic bridge message using reflection. But, if you provide an argument of a type other than NumericValue , the method will fail with a ClassCastException thanks of the cast in the implementation of the bridge method. Example (failed attempt to invoke bridge method via reflection): int reflectiveCompareTo(NumericValue value, Object other)The cast to type NumericValue in the bridge method fails with a ClassCastException when an argument of a type other than NumericValue is passed to the bridge method. This was it is guaranteed that a bridge method, even when it is called, will fail for unexpected argument types. |
|
| LINK TO THIS | #FAQ102 |
| REFERENCES |
What
is type erasure?
Under which circumstances is a bridge method generated? |
| When a type extends or implements a parameterized class or interface and type erasure changes the signature of any inherited method. | |
|
Bridge methods are necessary when a class implements a
parameterized interface or extends a parameterized superclass and type
ersure changes the argument type of any of the inherited non-static methods.
Below is an example of a class that extends a parameterized superclass. Example (before type erasure): class Superclass <T extends Bound> {Example (after type erasure): class Superclass {Type erasure changes the signature of the superclass's methods. The subclass's methods are no longer overriding versions of the superclass's method after type erasure. In order to make overriding work the compiler adds bridge methods. The compiler must add bridge methods even if the subclass does not override the inherited methods. Example (before type erasure): class Superclass <T extends Bound> {Example (after type erasure): class Superclass {The subclass is derived from a particular instantiation of the superclass and therefore inherits the methods with a particular signature. After type erasure the signature of the superclass's methods are different from the signatures that the subclass is supposed to have inherited. The compiler adds bridge methods, so that the subclass has the expected inherited methods. No bridge method is needed when type erasure does not change the signature of any of the methods of the parameterized supertype. Also, no bridge method is needed if the signatures of methods in the sub- and supertype change in the same way. This can occur when the subtype is generic itself. Example (before type erasure): interface Callable <V> {Example (after type erasure): interface Callable {The return type of the call method changes during type erasure in the interface and the implementing class. After type erasure the two methods have the same signature so that the subclass's method implements the interface's method without a brdige method. However, it does not suffice that the subclass is generic. The key is that the method signatures must not match after type erasure. Otherwise, we again need a bridge method. Example (before type erasure): interface Copyable <V> extends Cloneable {Example (after type erasure): interface Copyable extends Cloneable {The method signatures change to Object copy() in the interface and Copyable copy() in the subclass (because of the type parameter bound). As a result, the compiler adds a bridge method. |
|
| LINK TO THIS | #FAQ103 |
| REFERENCES | What is type erasure? |
| Because the return type of methods of a parameterized type might change as a side effect of type erasure. | |
|
During type erasure the compiler replaces type parameters
by the leftmost bound, or type
Object
if no bound was specified.
This means that methods whose return type is the type parameter would return
a reference that is either the leftmost bound or
Object
, instead
of the more specific type that was specified in the parameterized type
and that the caller expects. A cast is need from the leftmost bound
or
Object
down to the more specific type..
Example (before type erasure): public class Pair<X,Y> {Example (after type erasure): public class Pair {After type erasure the methods getFirst and getSecond of type Pair both have the return type Object . Since the declared static type of the pair in our test case is Pair<String,Long> the caller of getFirst and getSecond expects a String and a Long as the return value. Without a cast this would not work and in order to make it work the compiler adds the necessary casts from Object to String and Long respectively. The inserted casts cannot fail at runtime with a ClassCastException because the compiler already made sure at compile-time that both fields are references to objects of the expected type. The compiler would issue an error method if arguments of types other than String or Long had been passed to the constructor or the set methods. Hence it is guarantees that these casts cannot fail. In general, casts silently added by the compiler are guaranteed not to raise a ClassCastException if the program was compiled without warnings. This is the type-safety guarantee. Implicit casts are inserted when methods are invoked whose return type changed during type erasure. Invocation of methods whose argument type changed during type erasure do not require insertion of any casts. For instance, after type erasure the setFirst and setSecond methods of class Pair take Object arguments. Invoking them with arguments of a more specific type such as String and Long is possible without the need for any casts. |
|
| LINK TO THIS | #FAQ104 |
| REFERENCES |
What
is type erasure?
What does type-safety mean? |
| The compiler adds casts as needed. | |
|
In the process of type erasure the compiler replaces type
parameters by their leftmost bound, or type
Object
if no bound
was specified. How does that work if a type parameter has several bounds?
Example (before type erasure): interface Runnable {Example (after type erasure): interface Runnable {The type parameter T is replaced by the bound Callable , which means that both fields are held as references of type Callable . Methods of the leftmost bound (which is Callable in our example) can be called directly. For invocation of methods of the other bounds ( Runnable in our example) the compiler adds a cast to the respective bound type, so that the methods are accessible. The inserted cast cannot fail at runtime with a ClassCastException because the compiler already made sure at compile-time that both fields are references to objects of a type that is within both bounds. In general, casts silently added by the compiler are guaranteed not to raise a ClassCastException if the program was compiled without warnings. This is the type-safety guarantee. |
|
| LINK TO THIS | #FAQ105 |
| REFERENCES | What does type-safety mean? |
| A type whose type information is fully available at runtime, that is, a type that does not lose information in the course of type erasure. | |
|
As a side effect of type erasure, some type information
that is present in the source code is no longer available at runtime.
For instance, parameterized types are translated to their corresponding
raw type in a process called
type erasure
and lose the information
regarding their type arguments.
For example, types such as List<String> or Pair<? extends Number, ? extends Number> are available to and used by the compiler in their exact form, including the type argument information. After type erasure, the virtual machine has only the raw types List and Pair available, which means that part of the type information is lost. In contrast, non-parameterized types such as java.util.Date or java.lang.Thread.State are not affected by type erasure. Their type information remains exact, because they do not have type arguments. Among the instantiations of a generic type only the unbounded wildcard instantiations, such as Map<?,?> or Pair<?,?> , are unaffected by type erasure. They do lose their type arguments, but since all type arguments are unbounded wildcards, no information is lost. Types that do NOT lose any information during type erasure are called reifiable types . The term reifiable stems from reification . Reification means that type parameters and type arguments of generic types and methods are available at runtime. Java does not have such a runtime representation for type arguments because of type erasure. Consequently, the reifiable types in Java are only those types for which reification does not make a difference, that is, the types that do not need any runtime representation of type arguments. The following types are reifiable:
|
|
| LINK TO THIS | #FAQ106 |
| REFERENCES |
What
is type erasure?
What is reification? What is an unbounded wildcard parameterized type? What is the raw type? Which types can or must not appear as target type in an instanceof expression? Can I create an array whose component type is a concrete parameterized type? Can I create an array whose component type is a wildcard parameterized type? Why is it allowed to create an array whose component type is an unbounded wildcard parameterized type? |