Angelika Langer - Training & Consulting

 
HOME | SEMINARS | TALKS | ARTICLES | BOOKS | LINKS | IOSTREAMS | GENERICS | ABOUT | NEWSLETTER | CONTACT | SITEMAP
Java Generics FAQs - Under The Hood Of The Compiler

Books  
HOME 
SEMINARS 
TALKS 
ARTICLES 
BOOKS 
LINKS 
IOSTREAMS 

GENERICS
  FAQ    
    CONTENT
    FUNDAMENTALS
    FEATURES
    PRACTICAL
    TECHNICAL
    INFORMATION
    GLOSSARY
    INDEX
    PDF VERSION
 
ABOUT 
NEWSLETTER 
CONTACT 
SITEMAP 
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.
A printable version of the FAQ documents is available in PDF format (2.5MB).


 

Java Generics FAQs - Under The Hood Of The Compiler

Technicalities - Under The Hood Of The Compiler

© Copyright 2003-2007 by Angelika Langer.  All Rights Reserved.
Compiler Messages Heap Pollution Type Erasure Type System Exception Handling Static Context Type Argument Inference Wilcard Capture Wildcard Instantiations Cast and instanceof Overloading and Overriding

Under The Hood Of The Compiler


 

Compiler Messages

What is an "unchecked" warning?

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();
set.add("abc");        // unchecked warning
set.remove("abc");


warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.TreeSet
               set.add("abc"); 
                      ^
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): 

class Wrapper<T> {
  private T wrapped ;
  public Wrapper (T arg) {wrapped = arg;}
  ...
  p ublic Wrapper <T> clone() {
    Wrapper<T> clon = null;
     try { 
       clon = (Wrapper<T>) super.clone(); // unchecked warning
     } catch (CloneNotSupportedException e) { 
       throw new InternalError(); 
     }
     try { 
       Class<?> clzz = this.wrapped.getClass();
       Method   meth = clzz.getMethod("clone", new Class[0]);
       Object   dupl = meth.invoke(this.wrapped, new Object[0]);
       clon.wrapped = (T) dupl; // unchecked warning
     } catch (Exception e) {}
     return clon;
  }
}


warning: [unchecked] unchecked cast
found   : java.lang.Object
required: Wrapper <T>
                  clon = ( Wrapper <T>)super.clone(); 
                                                ^
warning: [unchecked] unchecked cast
found   : java.lang.Object
required: T
                  clon. wrapped = (T)dupl; 
                                    ^
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?

What is the -Xlint:unchecked compiler option?

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.
Note: Recompile with -Xlint:unchecked for details.
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.java
The 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.java
In 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?

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): 

@SuppressWarnings("unchecked")
class Wrapper<T> {
  private T wrapped ;
  public Wrapper (T arg) {wrapped = arg;}
  ...
  p ublic Wrapper <T> clone() {
    Wrapper<T> clon = null;
     try { 
       clon = (Wrapper<T>) super.clone(); // unchecked warning supressed
     } catch (CloneNotSupportedException e) { 
       throw new InternalError();
     }
     try { 
       Class<?> clzz = this.wrapped.getClass();
       Method   meth = clzz.getMethod("clone", new Class[0]);
       Object   dupl = meth.invoke(this.wrapped, new Object[0]);
       clon.wrapped = (T) dupl; // unchecked warning supressed
     } catch (Exception e) {}
     return clon;
  }
}
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): 

@SuppressWarnings(value={"unchecked","deprecation"})
public static void someMethod() {
  ...
  TreeSet se t = new TreeSet();
  set.add(new Date(104,8,11));     // unchecked and deprecation warning suppressed
  ...
}
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 deprecated
        set.add(new Date(104,8,11));
                ^
warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.TreeSet
        set.add(new Date(104,8,11));
 The 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() {
  ...
  TreeSet se t = new TreeSet(); 
  @SuppressWarnings(value={"unchecked"}) // error
  set.add(new Date(104,8,11)); 
  ...
}
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?

How can I avoid "unchecked cast" 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> {
  private T wrapped;
    ...
  public boolean equals (Object other) {
    ...
    Wrapper <T> other Wrapper = (Wrapper<T>) other; // warning; unchecked cast
    return (this.wrapped.equals( other Wrapper.wrapped));
  }
}
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> {
  private T wrapped;
    ...
  public boolean equals (Object other) {
    ...
    Wrapper <?> other Wrapper = (Wrapper<?>) other;
    return (this.wrapped.equals( other Wrapper.wrapped));
  }
}
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?

Is it possible to eliminate all "unchecked" warnings?

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?

Why do I get an "unchecked" warning although there is no type information missing?

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> {
  boolean add(E o) { ...  }
}
TreeSet se t = new TreeSet();
set.add("abc");        // unchecked warning

warning: [unchecked] unchecked call to add(E) as a member of the raw type TreeSet
               set.add("abc"); 
                      ^
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> {
  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(); 
                                             ^
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. 

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?

 
 

Heap Pollution

What is heap pollution?
 
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


After the assignment of the reference variable  ln to the reference variable ls , the  List<String> variable will point to a  List<Number> object. Such a situation is called  heap pollution and is usually indicated by an unchecked warning.  A polluted heap is likely to lead to an unexpected  ClassCastException at runtime.  In the example above, it will lead to a  ClassCastException , when a object is retrieved from the  List<String> and assigned to a  String variable, because the object is a  Number , not a  String .

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:
  • mixing raw types and parameterized types
  • performing unchecked casts
  • separate compilation of translation units
With the exception of separate compilation, the compiler will always issue an unchecked warning to draw your attention to the potential heap pollution. If you co-compile your code without warnings then no heap pollution can ever occur. 

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;
list = ln; 
// unchecked warning  + heap pollution
list = ls; // unchecked warning  + NO heap pollution


The first assignment leads to heap pollution, because the  List<String> variable would then point to a  List<Number> .  The second assignment does not result in heap pollution, because the raw type variable on the right-hand side of the assignment refers to a  List<String> , which matches the parameterized type on the left-hand side of the assignment.

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>();
List<Short> ls = (List<Short>) ln;  // unchecked warning  + heap pollution
List<Long>  ll = (List<Long>)  ln;  // unchecked warning  + NO heap pollution

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.

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));
    }

}


The program compiles and runs fine.  Now, let's assume that we modify the  FileCrawler implementation.  Instead of returning a  List<String> we return a  List<StringBuilder> .  Note, the other class is not changed at all.

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 {
    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));
    }
}

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> .

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?

Type Erasure

How does the compiler translate Java generics?
 
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 specialization. The compiler generates a new representation for every instantiation of a generic type or method. For instance, the compiler would generate code for a list of integers and additional, different code for a list of strings, a list of dates, a list of buffers, and so on. 

  • 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:
    The C++ compiler generates executable code for every instantiation of a template. The downside of code specialization of generic types is its potential for code bloat.  A list of integers and a list of strings would be represented in the executable code as two different types. Note that code bloat is not inevitable in C++ and can generally be avoided by an experienced programmer. 

    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?

    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.
    When the compiler finds the definition of a generic type or method, it removes all occurrences of the type parameters and replaces them by their leftmost bound, or type Object if no bound had been specified. 

    Eliding type arguments.
    When the compiler finds a paramterized type, i.e. an instantiation of a generic type, then it removes the type arguments. For instance, the types List<String> , Set<Long> , and Map<String,?> are translated to List , Set and Map respectively. 

    Example (before type erasure): 

    interface Comparable <A> {
      public int compareTo( A that);
    }
    final class NumericValue implements Comparable <NumericValue> {
      priva te byte value; 
      public  NumericValue (byte value) { this.value = value; } 
      public  byte getValue() { return value; } 
      public  int compareTo( NumericValue t hat) { return this.value - that.value; }
    }
    class Collections { 
      public static <A extends Comparable<A>>A max(Collection <A> xs) {
        Iterator <A> xi = xs.iterator();
        A w = xi.next();
        while (xi.hasNext()) {
          A x = xi.next();
          if (w.compareTo(x) < 0) w = x;
        }
        return w;
      }
    }
    final class Test {
      public static void main (String[ ] args) {
        LinkedList <NumericValue> numberList = new LinkedList <NumericValue> ();
        numberList .add(new NumericValue((byte)0)); 
        numberList .add(new NumericValue((byte)1)); 
        NumericValue y = Collections.max( numberList ); 
      }
    }
    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 {
      public int compareTo( Object that);
    }
    final class NumericValue implements Comparable {
      priva te byte value; 
      public  NumericValue (byte value) { this.value = value; } 
      public  byte getValue() { return value; } 
      public  int compareTo( NumericValue t hat)   { return this.value - that.value; }
      public  int compareTo(Object that) { return this.compareTo((NumericValue)that);  }
    }
    class Collections { 
      public static Comparable max(Collection xs) {
        Iterator xi = xs.iterator();
        Comparable w = (Comparable) xi.next();
        while (xi.hasNext()) {
          Comparable x = (Comparable) xi.next();
          if (w.compareTo(x) < 0) w = x;
        }
        return w;
      }
    }
    final class Test {
      public static void main (String[ ] args) {
        LinkedList numberList = new LinkedList();
        numberList .add(new NumericValue((byte)0)); 
        numberList .add(new NumericValue((byte)1)); 
        NumericValue y = (NumericValue) Collections.max( numberList ); 
      }
    }
    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?

    What is reification?

    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?

    What is a bridge method?

    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> {
      public int compareTo( A that);
    }
    final class NumericValue implements Comparable <NumericValue> {
      priva te byte value; 
      public  NumericValue (byte value) { this.value = value; }
      public  byte getValue() { return value; } 
      public  int compareTo( NumericValue t hat) { return this.value - that.value; }
    }
    In the example, class NumericValue implements interface Comparable<NumericValue> and must therefore override the superinterface&rsquo;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 {
      public int compareTo( Object that);
    }
    final class NumericValue implements Comparable {
      priva te byte value; 
      public  NumericValue (byte value) { this.value = value; } 
      public  byte getValue() { return value; } 
      public  int compareTo( NumericValue t hat)   { return this.value - that.value; }
      public  int compareTo(Object that) { return this.compareTo((NumericValue)that);  }
    }
    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&rsquo;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);
    value.compareTo(value);  // fine
    value.compareTo("abc");  // error
    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)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
      Method meth = NumericValue.class.getMethod("compareTo", new Class[]{Object.class});
      return (Integer)meth.invoke(value, new Object[]{other}); 
    }
    NumericValue value = new NumericValue((byte)0);
    reflectiveCompareTo(value, value);  // fine
    reflectiveCompareTo(value,"abc");   // ClassCastException
    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?

    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> {
      public void m1( T arg) { ... }
      public T m2() { ... }
    }
    class Subclass extends Superclass <SubTypeOfBound> {
       public void m1( SubTypeOfBound arg) { ... }
       public SubTypeOfBound m 2() { ... }
    Example (after type erasure): 
    class Superclass {
      void m1( Bound arg) { ... }
      Bound m2() { ... }

    class Subclass extends Superclass {
      public void m1(SubTypeOfBound arg) { ... }
      public void m1(Bound arg) { m1((SubTypeOfBound)arg); }
      public SubTypeOfBound m2() { ... }
      public Bound          m2() { return m2(); }
    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> {
      public void m1( T arg) { ... }
      public T m2() { ... }
    }
    class AnotherSubclass extends Superclass <SubTypeOfBound> {
    }
    Example (after type erasure): 
    class Superclass {
      void m1( Bound arg) { ... }
      Bound m2() { ... }
    }
    class AnotherSubclass extends Superclass {
      public void  m1(Bound arg) { super.m1((SubTypeOfBound)arg); }
      public Bound m2() { return super.m2(); }
    }
    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> {
      public V call();
    }
    class Task <T> implements Callable <T> {
      public T call() { ... }
    }
    Example (after type erasure): 
    interface Callable {
      public Object call();
    }
    class Task implements Callable {
      public Object call() { ... }
    }
    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 {
      public V copy();
    }
    class Triple <T extends Copyable<T>> implements Copyable <Triple<T>> {
      public Triple<T> copy() { ... }
    }
    Example (after type erasure): 
    interface Copyable extends Cloneable {
      public Object copy();
    }
    class Triple implements Copyable {
      public Copyable copy() { ... }
      public Triple   copy() { return copy(); }
    }
    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?

    Why does the compiler add casts when it translates generics?

    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> {
      private X first;
      private Y second;
      public Pair(X x, Y y) {
        first = x;
        second = y;
      }
      public X getFirst() { return first; }
      public Y getSecond() { return second; }
      public void setFirst(X x) { first = x; }
      public void setSecond(Y y) { second = y; }
    }

    final class Test {
      public static void main(String[] args) {
        Pair<String,Long> pair = new Pair<String,Long>("limit", 10000L);
        String s = pair.getFirst();
        Long   l = pair.getSecond();
        Object o = pair.getSecond();
      }
    }

    Example (after type erasure): 
    public class Pair {
      private Object first;
      private Object second;
      public Pair( Object x, Object y) {
        first = x;
        second = y;
      }
      public Object getFirst() { return first; }
      public Object getSecond() { return second; }
      public void setFirst( Object x) { first = x; }
      public void setSecond( Object y) { second = y; }
    }

    final class Test {
      public static void main(String[] args) {
        Pair pair = new Pair("limit", 10000L);
        String s = (String) pair.getFirst();
        Long   l = (Long)    pair.getSeond();
        Object o =          pair.getSecond();
      }
    }

    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?

    How does type erasure work when a type parameter has several bounds?

    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 { 
      void run();
    }
    interface Callable<V> {
      V call();
    }
    class X<T extends Callable<Long> & Runnable> {
      private T task1, task2;
      ...
      public void do() { 
        task1.run(); 
        Long result = task2.call();
      }
    Example (after type erasure): 
    interface Runnable { 
      void run();
    }
    interface Callable {
      Object call();
    }
    class X {
      private Callable task1, task2;
      ...
      public void do() { 
        ( (Runnable) task1).run(); 
        Long result = (Long) task2.call();
      }
    }
    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?

    What is a reifiable type?

    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: 

    • primitive types
    • non-generic (or non-parameterized) reference types
    • unbounded wildcard instantiations
    • raw types
    • arrays of any of the above
    The non-reifiable types, which lose type information as a side effect of type erasure, are: 
    • instantiations of a generic type with at least one concrete type argument
    • instantiations of a generic type with at least one bounded wildcard as type argument
    Reifiable types are permitted in some places where non-reifiable types are disallowed.  Reifiable types are permitted (and non-reifiable types are prohibited): 
    • as type in an instanceof expression
    • as component type of an array
    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?

    What is the type erasure of a parameterized type?