How compilers in various languages handle generics (detailed java generics)

1. How compilers in various languages ​​​​handle generics

Typically, a compiler handles generics in two ways:

1. Code specialization. A new object code (bytecode or binary code) is generated when instantiating a generic class or generic method. For example, for a generic type , Listit may be necessary to generate three object codes for String, .IntegerFloat

2. Code sharing. Only a unique object code is generated for each generic class; all instances of the generic class are mapped to this object code, and type checking and type conversion are performed when necessary.

Templates ( ) in C++template are the typical Code specializationimplementation. The C++ compiler will generate an execution code for each generic class instance. Executive code neutralization Integer Listis String Listtwo different types. This can lead to code bloat . Generics in C# exist in the source code of the program, in the compiled IL(Intermediate Language, intermediate language, at this time, the generic is a placeholder) or in the CLR at runtime, List<Integer>and List<String>are two different Types, which are generated during system runtime, have their own virtual method table and type data. This implementation is called type expansion, and the generics implemented based on this method are called 真实泛型. The generic type in the Java language is different. It only exists in the program source code. In the compiled bytecode file, it has been replaced by the original native type (Raw Type, also known as the naked type), and The mandatory transformation code is inserted in the corresponding place, so for the Java language at runtime, ArrayList<Integer>and ArrayList<String>is the same class. Therefore, generic technology is actually a grammatical sugar of the Java language. The generic implementation method in the Java language is called type erasure , and the generics implemented based on this method are called 伪泛型.

C++and C#is Code specializationthe processing mechanism used. As mentioned earlier, it has a disadvantage, that is, it will cause code bloat . Another disadvantage is that in the reference type system, space is wasted, because the elements in the reference type collection are essentially pointers. It is not necessary to generate an implementation code for each type. Code sharingAnd this is the main reason why generics are handled in a way in the Java compiler .

JavaThe compiler Code sharingcreates a unique bytecode representation for each generic type, and maps instances of the generic type to this unique bytecode representation. The mapping of multiple generic type instances to unique bytecode representations is accomplished through type erasure ( ).type erasure


2. What is type erasure

We mentioned this word many times before: type erasure ( type erasure), so what exactly is type erasure?

Type erasure refers to associating generic type instances with the same bytecode through type parameter merging. The compiler only generates a bytecode for the generic type and associates its instance with this bytecode. The key to type erasure is to clear information about type parameters from generic types, and to add methods for type checking and type conversion when necessary. Type erasure can be simply understood as converting generic java code into ordinary java code, but the compiler is more direct and directly converts generic java code into ordinary java bytecode. The main process of type erasure is as follows: 1. Replace all generic parameters with their leftmost boundary (topmost parent type) type. (This part can be seen in: [Understanding of extends and super in Java generics][2]) 2. Remove all type parameters.


3. The process of Java compiler processing generics

code 1:

public static void main(String[] args) {  
    Map<String, String> map = new HashMap<String, String>();  
    map.put("name", "hollis");  
    map.put("age", "22");  
    System.out.println(map.get("name"));  
    System.out.println(map.get("age"));  
}  

Decompiled code 1:

public static void main(String[] args) {  
    Map map = new HashMap();  
    map.put("name", "hollis");  
    map.put("age", "22"); 
    System.out.println((String) map.get("name"));  
    System.out.println((String) map.get("age"));  
}  

We found that the generics are gone, and the program has changed back to the way it was written before the appearance of Java generics, and the generic types have changed back to native types.


code 2:

interface Comparable<A> {
    public int compareTo(A that);
}

public final class NumericValue implements Comparable<NumericValue> {
    private byte value;

    public NumericValue(byte value) {
        this.value = value;
    }

    public byte getValue() {
        return value;
    }

    public int compareTo(NumericValue that) {
        return this.value - that.value;
    }
}

Decompiled code 2:

 interface Comparable {
  public int compareTo( Object that);
} 

public final class NumericValue
    implements Comparable
{
    public NumericValue(byte value)
    {
        this.value = value;
    }
    public byte getValue()
    {
        return value;
    }
    public int compareTo(NumericValue that)
    {
        return value - that.value;
    }
    public volatile int compareTo(Object obj)
    {
        return compareTo((NumericValue)obj);
    }
    private byte value;
}

code 3:

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

Decompiled code 3:

public class Collections
{
    public 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;
    }
}

After the second generic class Comparable <A>is erased, A is replaced with the leftmost boundary Object. Comparable<NumericValue>The type parameter NumericValueis erased, but this directly results in NumericValuethe interface method not being implemented Comparable的compareTo(Object that), so the compiler, being the good guy, adds a bridge method . In the third example, the boundary of the type parameter is defined <A extends Comparable<A>>A. A must be Comparable<A>a subclass. According to the process of type erasure, first all the type parameters ti are changed to the leftmost boundary Comparable<A>, and then the parameter type is removed Ato obtain the final erasure result.


Fourth, the problems caused by generics

1. When generics encounter overloading:

public class GenericTypes {  

    public static void method(List<String> list) {  
        System.out.println("invoke method(List<String> list)");  
    }  

    public static void method(List<Integer> list) {  
        System.out.println("invoke method(List<Integer> list)");  
    }  
}  

In the above code, there are two overloaded functions, because their parameter types are different, one is List<String>the other List<Integer>, but this code cannot be compiled. Because as we said before, the parameters List<Integer>and List<String>compilation are erased and become the same native type List. The erase action causes the feature signatures of the two methods to become exactly the same.

Second, when generics encounter catch:

If we customize a generic exception class GenericException, then don't try to use multiple catches to match different exception types. For example, if you want to catch GenericException and GenericException separately, this is also problematic.

3. When the generic contains static variables

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

The answer is - 2! Due to type erasure, all generic class instances are associated with the same bytecode, and all static variables of the generic class are shared.


V. Summary

1. There are no generics in the virtual machine, only ordinary classes and ordinary methods. All type parameters of generic classes will be erased at compile time. Generic classes do not have their own unique Class objects. For example, there is no List<String>.class or List<Integer>.class, but only List.class. 2. Please specify the type when creating a generic object, and let the compiler check the parameters as soon as possible ( Effective Java, item 23: Please do not use the original ecological type in the new code ) 3. Do not ignore the warning message of the compiler, which means Potential ClassCastExceptionawaits you. 4. Static variables are shared by all instances of the generic class. For MyClass<T>a class declared as , the method to access the static variables in it is still MyClass.myStaticVar. Regardless of whether it is passed new MyClass<String>or new MyClass<Integer>created, a static variable is shared. 5. Generic type parameters cannot be used in Javaexception handling catchstatements. Because exception handling is performed by the JVM at runtime. Since the type information is erased, it is impossible to distinguish between JVMtwo exception types . For , they are all types. It is also impossible to execute the statement corresponding to the exception .MyException<String>MyException<Integer>JVMMyExceptioncatch

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132307509