Home  >  Article  >  Java  >  Java Generics - Type Erasure

Java Generics - Type Erasure

高洛峰
高洛峰Original
2016-12-19 15:35:051464browse

1. Overview

There are many problems when using Java generics, such as List.class does not exist, List cannot be assigned to List (not covariable), strange ClassCastException, etc. The correct use of Java generics requires an in-depth understanding of some Java concepts, such as covariance, bridging methods, and type erasure as documented in this note. Java generics are almost all processed in the compiler. The bytecode generated by the compiler does not contain generic information. The generic type information will be erased during compilation. This process is called type erasure.

2. How does the compiler handle generics?

Usually, a compiler handles generics in two ways:
1.Code specialization. When instantiating a generic class or generic method, a new object code (bytecode or binary code) is generated. For example, for a generic list, it may be necessary to generate three target codes for string, integer, and float.
2.Code sharing. Only a unique copy of the target code is generated for each generic class; all instances of the generic class are mapped to this target code, and type checking and type conversion are performed when necessary.
Template in C++ is a typical Code specialization implementation. The C++ compiler will generate an execution code for each generic class instance. Integer list and string list are two different types in the execution code. This will lead to code bloat, but experienced C++ programmers can skillfully avoid code bloat.
Another drawback of Code specialization is the waste of space in the reference type system, because the elements in the reference type collection are essentially pointers. There is no need to generate an execution code for each type. This is also the main reason why the Java compiler uses Code sharing to handle generics.
The Java compiler creates a unique bytecode representation for each generic type through Code sharing, and maps instances of the generic type to this unique bytecode representation. Mapping multiple generic type instances to a unique bytecode representation is accomplished through type erasue.

3. What is type erasure?

Type erasure refers to associating generic type instances to the same bytecode through type parameter merging. The compiler generates only one bytecode for the generic type and associates its instances with this bytecode. The key to type erasure is to clear information about type parameters from generic types, and to add type checking and type conversion methods when necessary.
Type erasure can be simply understood as converting generic java code into ordinary java code, but the compiler is more direct, converting generic java code directly into ordinary java bytecode.
The main process of type erasure is as follows:
1. Replace all generic parameters with their leftmost boundary (top-level parent type) type.
2. Remove all type parameters.
For example, the type of

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 );    
  }   
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

after type erasure is
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 );    
  }   
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

The first generic class Comparable After erasure, A is replaced with the leftmost boundary Object. The type parameter NumericValue of Comparable was erased, but this directly caused NumericValue to not implement the compareTo(Object that) method of the interface Comparable, so the compiler acted as a good guy and added a bridge method.
The second example limits the boundary of type parameters
>A. A must be a subclass of Comparable. According to the process of type erasure, all type parameters ti are replaced first. is the leftmost boundary Comparable, and then removes the parameter type A to obtain the final erased result.

4. Problems caused by type erasure

It is precisely because of the hidden existence of type erasure that it directly leads to many generic supernatural problems.
Q1. Differentiate method signatures using instances of the same generic class? ——NO!
Import java.util.*;

    public class Erasure{  
  
            public void test(List<String> ls){  
                System.out.println("Sting");  
            }  
            public void test(List<Integer> li){  
                System.out.println("Integer");  
            }  
    }<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

Compile this class,

Java Generics - Type Erasure

参数类型明明不一样啊,一个List,一个是List,但是,偷偷的说,type erasure之后,它就都是List了⋯⋯
Q2. 同时catch同一个泛型异常类的多个实例?——NO!
同理,如果定义了一个泛型一场类GenericException,千万别同时catch GenericException和GenericException,因为他们是一样一样滴⋯⋯
Q3.泛型类的静态变量是共享的?——Yes!
猜猜这段代码的输出是什么?

import java.util.*;  
  
public class StaticTest{  
    public static void main(String[] args){  
        GT<Integer> gti = new GT<Integer>();  
        gti.var=1;  
        GT<String> gts = new GT<String>();  
        gts.var=2;  
        System.out.println(gti.var);  
    }  
}  
class GT<T>{  
    public static int var=0;  
    public void nothing(T x){}  
}<span style="color: #333333; font-family: Arial; font-size: 14px;">  
</span>

答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。

五、Just remember

1.虚拟机中没有泛型,只有普通类和普通方法
2.所有泛型类的类型参数在编译时都会被擦除
3.创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型)
4.不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。



更多Java泛型-类型擦除相关文章请关注PHP中文网!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn