Reference: core Java Technologies
I. Java generic implementation method: Type Erasure
As mentioned above, Java generics are pseudo generics. Why is Java's generics pseudo-generics? Because all generic information is erased during compilation. The primary prerequisite for a correct understanding of the generic concept is to understand the type erasure ).
In Java, generics are basically implemented at the compiler level. The generated Java bytecode does not contain type information in generics. The type parameter added when the generic type is used will be removed by the compiler during compilation. This process is called type erasure.
For example, the List <Object> and list <string> types defined in the Code are all programmed after compilation. What JVM sees is only list, and the type information appended by generics is invisible to JVM. The Java compiler tries its best to discover possible errors during compilation, but it still cannot avoid type conversion exceptions at runtime. Type erasure is also an important difference between Java generic implementation methods and C ++ template implementation methods.
Two simple examples are provided to illustrate the type erasure of Java generics.
Example 1,
public class Test4 {public static void main(String[] args) {ArrayList<String> arrayList1=new ArrayList<String>();arrayList1.add("abc");ArrayList<Integer> arrayList2=new ArrayList<Integer>();arrayList2.add(123);System.out.println(arrayList1.getClass()==arrayList2.getClass());}}
In this example, we define two arraylist arrays, but one is an arraylist <string> generic type, which can only store strings. One is the arraylist <integer> generic type and can only store integer data. Finally, we use the getclass method of the arraylist1 object and the arraylist2 object to obtain information about their classes. The final result is true. This indicates that the string and integer types of the generic type are erased, and only the original type is left.
Example 2,
Public class test4 {public static void main (string [] ARGs) throws exceptions, securityexception, illegalaccessexception, invocationtargetexception, nosuchmethodexception {arraylist <integer> arraylist3 = new arraylist <integer> (); arraylist3.add (1); // in this way, the add method can only store integer data, because the generic type instance is integerarraylist3.getclass (). getmethod ("add", object. class ). invoke (arraylist3, "ASD"); For (INT I = 0; I <arraylist3.size (); I ++) {system. out. println (arraylist3.get (I ));}}
An arraylist generic type is defined in the program as an integer object. If the add method is called directly, only integer data can be stored. However, when we call the Add Method Using Reflection, we can store strings. This indicates that the integer generic instance is erased after compilation and only the original type is retained.
Ii. Original Type retained after type Erasure
The original type is mentioned twice. What is the original type? The raw type deletes the generic information, and the real type of the type variable in the bytecode. Whenever a generic type is defined, the corresponding original type is automatically provided. The type variable is erased (crased) and replaced with its limited type (an infinitely variable is replaced with an object.
Example 3:
class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; }}
The original pair <t> type is:
class Pair {private Object value;public Object getValue() {return value;}public void setValue(Object value) {this.value = value;}}
In pair <t>, t is an infinitely variable of the type, so replace it with an object. The result is a common class, as it has been implemented before generics are added to Java to become a language. Different types of pair can be included in the program, such as pair <string> or pair <integer>. However, after erasure, the pair types become the original pair types, and the original types are all objects.
From the above example 2, we can also understand that after the arraylist <integer> is erased, the original type is changed to object, so we can store strings through reflection.
If the type variable is limited, replace the original type with the type variable of the first boundary.
Such as pair
Example 4:
public class Pair<T extends Comparable& Serializable> {
The original type is comparable.
Note:
If pair declares public class pair <t extends serializable & comparable> like this, the original type will be replaced with serializable, And the compiler will insert forced type conversion to comparable when necessary. To improve efficiency, you should put the tagging interface (that is, the interface without a method) at the end of the boundary limitation List.
The types of original and generic variables must be distinguished.
When calling a generic method, you can specify a generic type or do not specify a generic type.
If no generic type is specified, the generic variable type is the minimum level of the same parent class of several types in this method until the object.
When specifying the generic type, several types in this method must be the generic instance type or its subclass.
Public class Test2 {public static void main (string [] ARGs) {/** when the generic type is not specified */INT I = test2.add (1, 2 ); // both parameters are integer, so t is of the integer type number f = test2.add (1, 1.2); // the two parameters are integer and float in style, therefore, the minimum level of the same parent class is numberobject o = test2.add (1, "ASD"); // the two parameters are integer and float in style, therefore, the minimum level of the same parent class is used. When the wildcard is specified for Object/**, */int A = test2. <integer> Add (1, 2 ); // an integer is specified, so it can only be of the integer type or its subclass int B = test2. <integer> Add (1, 2.2); // a compilation error occurs. An integer is specified, cannot be floatnumber c = test2. <number> Add (1, 2.2); // specify as number, so it can be integer and float} // This is a simple generic method public static <t> t add (t x, t y) {return y ;}}
In a generic class, it is similar when no generic type is specified, except that the generic type is object at this time. For example, if the generic type is not specified in arraylist, the arraylist can contain any type of objects.
Example:
public static void main(String[] args) {ArrayList arrayList=new ArrayList();arrayList.add(1);arrayList.add("121");arrayList.add(new Date());}Iii. problems caused by type erasure and Solutions
For various reasons, Java cannot implement a true generic type. It can only use the type erasure to implement a pseudo generic type. Although there is no Type Expansion problem, it also raises many new problems. Therefore, Sun imposes many restrictions on these problems to prevent us from making various mistakes.
1. Check first, compile, and check the problem of the compiled object and reference transfer.
Since the type variables will be erased during compilation, why should we go to arraylist <string> arraylist = new arraylist <string> (); In the arraylist of the created array, can't I use the add method to add an integer? Isn't it that the generic variable integer will be erased to the original type object during compilation? Why can't it save another type? Since the type is erased, how can we ensure that only types limited by generic variables can be used?
How does Java solve this problem? The Java compiler checks the generic type in the code first, and then erases the type before compiling.
For example:
Public static void main (string [] ARGs) {arraylist <string> arraylist = new arraylist <string> (); arraylist. add ("123"); arraylist. add (123); // compilation error}
In the above program, use the add method to add an integer. In eclipse, an error is reported directly, indicating that this is the check before compilation. If it is checked after compilation and the original type is object after type erasure, any reference type should be added. But this is not the case. This just shows that the use of generic variables will be checked before compilation.
Who is this type of check? Let's take a look.Compatibility between parameterized types and original types
Taking arraylist as an example, the previous method is as follows:
ArrayList arrayList=new ArrayList();
Current statement:
ArrayList<String> arrayList=new ArrayList<String>();
If it is compatible with the previous Code, the following situation will inevitably occur between various reference values:
Arraylist <string> arraylist1 = new arraylist (); // first case
Arraylist arraylist2 = new arraylist <string> (); // The second case
There is no error in this case, but there will be a compile-time warning.
However, in the first case, the same effect can be achieved with the full use of generic parameters, while in the second case, the effect is completely ineffective.
Because, the original type check is completed during compilation. New arraylist () only opens a bucket in the memory and can store any type of objects. What really involves the type check is its reference, because we use it to reference arraylist1 to call its method, such as calling the add () method. Therefore, arraylist1 reference can complete the generic type check.
The referenced arraylist2 does not use generics, so it does not work.
For example:
Public class test10 {public static void main (string [] ARGs) {// arraylist <string> arraylist1 = new arraylist (); arraylist1.add ("1 "); // use arraylist1.add (1) for compilation. // The compilation error string str1 = arraylist1.get (0); // The returned type is stringarraylist arraylist2 = new arraylist <string> (); arraylist2.add ("1"); // compiled by arraylist2.add (1); // compiled by object = arraylist2.get (0 ); // The return type is objectnew arraylist <string> (). add ("11"); // compiled by new arraylist <string> (). add (22); // compilation error string = new arraylist <string> (). get (0); // The return type is string }}
Through the above example, we can understand that the type check is for reference, who is a reference, use this reference to call the generic method, the system checks the type of the method called by reference, regardless of the objects actually referenced by the method.
Here, we can further discuss why the inheritance relationship is not taken into account for parameterized types in generics.
In Java, the following form of reference transfer is not allowed:
Arraylist <string> arraylist1 = new arraylist <Object> (); // compilation error arraylist <Object> arraylist1 = new arraylist <string> (); // compilation Error
Let's first look at the first case and extend the first case to the following form:
Arraylist <Object> arraylist1 = new arraylist <Object> (); arraylist1.add (new object (); arraylist1.add (new object (); arraylist <string> arraylist2 = arraylist1; // compilation Error
In fact, there will be a compilation error when there are 4th lines of code. So let's assume that the compilation is correct. When we use the get () method to reference the value of arraylist2, all objects of the string type are returned (as mentioned above, the type detection is determined by reference .), However, the object type has actually been stored in it, so there will be classcastexception. So to avoid this kind of easy-to-occur error, Java does not allow such reference transfer. (This is also the reason for the emergence of generics, that is, to solve the type conversion problem, we cannot violate its original intention ).
In the second case, expand the second case to the following form:
Arraylist <string> arraylist1 = new arraylist <string> (); arraylist1.add (new string (); arraylist1.add (new string (); arraylist <Object> arraylist2 = arraylist1; // compilation Error
Yes, this is better than the first case. At the very least, classcastexception does not occur when we use the value of arraylist2, because it is converted from string to object. However, what is the significance of doing so? The reason for the emergence of generics is to solve the problem of type conversion. We used generics. In the end, we had to turn strong on our own, violating the original intention of generic design. So JAVA does not allow this. Besides, if you use arraylist2 to add () a new object to it, how do I know whether the object type is string or object?
Therefore, pay special attention to the problem of reference transfer in generics.
2. Automatic type conversion
Due to the type erasure problem, all generic type variables are replaced with the original type. This leads to a problem. Since all are replaced with the original type, why do we not need to perform forced type conversion when obtaining it? Let's take a look at the arraylist and get methods:
public E get(int index) {RangeCheck(index);return (E) elementData[index]; }
We can see that the conversion is strong according to the generic variables before return. Assume that the generic type variable is date. Although the generic information is erased, the (e) elementdata [Index] is compiled into (date) elementdata [Index]. Therefore, we do not need to perform strong conversion on our own.
When a wildcard domain is accessed, forced type conversion is automatically inserted. If the value field of the pair class is public, the expression is as follows:
Date date=pair.value
Forced type conversion is automatically inserted into the result bytecode.
3. Conflict between type erasure and polymorphism and Solution
Now there is such a generic class:
class Pair<T> {private T value;public T getValue() {return value;}public void setValue(T value) {this.value = value;}}
Then we want a subclass to inherit it.
class DateInter extends Pair<Date> {@Overridepublic void setValue(Date value) {super.setValue(value);}@Overridepublic Date getValue() {return super.getValue();}}
In this subclass, we set the generic type of the parent class to pair <date>. In the subclass, We overwrite the two methods of the parent class. Our original intent is as follows:
If the generic type of the parent class is limited to date, the parameters of the two methods in the parent class are of the date type:"
public Date getValue() {return value;}public void setValue(Date value) {this.value = value;}
Therefore, there is no problem at all when we re-write these two methods in the subclass. In fact, we can see from their @ override tag that there is no problem at all. Is this actually true?
Analysis:
In fact, after the type erasure, all the generic types of the parent class are changed to the original type object, so the parent class will become the following after compilation:
class Pair {private Object value;public Object getValue() {return value;}public void setValue(Object value) {this.value = value;}}
Let's look at the types of the two override methods of the subclass:
@Overridepublic void setValue(Date value) {super.setValue(value);}@Overridepublic Date getValue() {return super.getValue();}
First, we will analyze the setvalue method. The type of the parent class is object, and the type of the subclass is date. The parameter types are different. In this case, if there is a normal inheritance relationship, it will not be overwritten, it is heavy load.
Let's test it in a main method:
Public static void main (string [] ARGs) throws classnotfoundexception {dateinter = new dateinter (); dateinter. setvalue (new date (); dateinter. setvalue (new object (); // compilation error}
If it is an overload, two setvalue methods in the subclass, one of which is the parameter object type and the other is the date type. However, we find that, there is no such method for a subclass to inherit the object type parameters of the parent class. Therefore, it is a rewrite, not a heavy load.
Why?
The reason is that the generic type of the parent class is date, pair <date>, and our intention is to change the generic class to the following:
class Pair {private Date value;public Date getValue() {return value;}public void setValue(Date value) {this.value = value;}}
Then, override the two methods whose parameter type is date in the subclass to realize polymorphism in inheritance.
For various reasons, the virtual machine cannot change the generic type to date, but can only erase the type and change it to the original type object. In this way, we intend to rewrite and implement polymorphism. However, after type erasure, it can only be changed to heavy load. In this way, the type erasure conflicts with polymorphism. Does JVM know your intention? Yes !!! But can it be implemented directly? No !!! If not, how can we rewrite the method of the date type parameter we want.
Therefore, JVM adopts a special method to complete this function, that is, the bridge method.
First, we decompile the bytecode of the dateinter subclass using the javap-C classname method. The result is as follows:
Class COM. tao. test. dateinter extends COM. tao. test. pair <Java. util. date> {COM. tao. test. dateinter (); Code: 0: aload_0 1: invokespecial #8 // method com/TAO/test/pair. "<init>" :() V 4: return public void setvalue (Java. util. date); // The setvalue method code we override: 0: aload_0 1: aload_1 2: invokespecial #16 // method com/TAO/test/pair. setvalue :( ljava/lang/object;) V 5: return public Java. util. date getvalue (); // the code of the getvalue method we override: 0: aload_0 1: invokespecial #23 // method com/TAO/test/pair. getvalue :() ljava/lang/object; 4: checkcast #26 // class Java/util/date 7: areturn public Java. lang. object getvalue (); // code generated by the compiler during compilation: 0: aload_0 1: invokevirtual #28 // method getvalue :() ljava/util/date calls the getvalue method we override; 4: areturn public void setvalue (Java. lang. object); // The code generated by the compiler during compilation: 0: aload_0 1: aload_1 2: checkcast #26 // class Java/util/date 5: invokevirtual #30 // method setvalue :( ljava/util/date; call the setvalue method we override) V 8: return}
According to the compilation results, we intend to rewrite the sub-classes of the setvalue and getvalue methods. There are actually four methods. In fact, there is no need to be surprised. The last two methods are the bridge method generated by the compiler itself. We can see that the parameter types of the bridge method are all objects. That is to say, the two methods that really overwrite the parent class in the subclass are the bridge methods that we don't see. The @ oveerride in the setvalue and getvalue methods we define is just an illusion. The internal implementation of the bridge method only calls the two methods we have rewritten.
Therefore, the virtual machine uses clever methods to solve the conflict between type erasure and polymorphism.
However, the setvalue and getvalue methods have different meanings.
The setvalue method is used to resolve the conflict between type erasure and polymorphism.
Getvalue has a general significance. How can we say that if it is a common inheritance relationship:
The setvalue method of the parent class is as follows:
public ObjectgetValue() {return super.getValue();}
The subclass rewrite method is as follows:
public Date getValue() {return super.getValue();}
In fact, this is also a common rewriting in general class inheritance, which is a covariant.
About covariant :......
In addition, there may be some questions. The clever methods in the subclass, object getvalue () and date getvalue (), exist at the same time. However, if they are two common methods, their method signatures are the same, that is, virtual machines cannot separate the two methods. If we write Java code by ourselves, such code cannot pass the compiler check, but the virtual machine allows this because the Virtual Machine determines a method through the parameter type and return type, therefore, in order to realize generic polymorphism, the compiler allows itself to do this seemingly "illegal" thing, and then gives it to the Virtual Machine for distinction.
4. Generic variables cannot be basic data types.
You cannot replace the basic type with the type parameter. For example, there is no arraylist <double>, and only arraylist <double>. After the data type is erased, the original type of arraylist is changed to object. However, the object type cannot store double values and can only reference double values.
5. Run-Time type Query
For example:
ArrayList<String> arrayList=new ArrayList<String>();
After the data type is erased, arraylist <string> only contains the original data type. The string of the generic information does not exist.
The following method is incorrect when you perform type query during running.
if( arrayList instanceof ArrayList<String>)
Java limits this type of query method
if( arrayList instanceof ArrayList<?>)
? Is a wildcard, which will be described in the following section.
6. Usage of generics in exceptions
1. Objects of generic classes cannot be thrown or captured. In fact, generic extension throwable is invalid. For example, the following definition will not be compiled:
public class Problem<T> extends Exception{......}
Why can't throwable be extended? Because exceptions are captured and thrown at runtime, and all generic information will be erased during compilation, assuming that the above compilation is feasible, then, let's look at the following definition:
try{}catch(Problem<Integer> e1){。。}catch(Problem<Number> e2){...}
After the type information is erased, the catch in both places is changed to the original type object. That is to say, the catch in both places is identical, which is equivalent to the following:
try{}catch(Problem<Object> e1){。。}catch(Problem<Object> e2){...
Of course, this cannot be done. For example, catch two common exceptions that are identical and cannot be compiled as follows:
Try {} catch (exception E1 ){..} Catch (exception E2) {// compilation error...
2. You cannot use generic variables in catch clauses.
Public static <t extends throwable> void dowork (class <t> T) {try {...} catch (t e) {// compilation error ...}}
Because the generic information has changed the original type during compilation, that is to say, the above T will change to the original type throwable. If you can use the generic variable in the catch clause, then, the following definition:
Public static <t extends throwable> void dowork (class <t> T) {try {...} catch (t e) {// compilation error ...} catch (indexoutofbounds e ){}}
According to the exception capture principle, the Child class must be at the beginning and the parent class is at the end, which violates this principle. Even if you use the static method t as arrayindexoutofbounds, it will still become throwable after compilation. arrayindexoutofbounds is the subclass of indexoutofbounds, which violates the exception capture principle. To avoid this situation, Java prohibits the use of generic variables in catch clauses.
However, you can use the type variable in the exception declaration. The following method is valid.
public static<T extends Throwable> void doWork(T t) throws T{ try{ ... }catch(Throwable realCause){ t.initCause(realCause); throw t; } }
There is no problem with the above usage.
7. Array (this is a problem caused by type erasure)
Arrays of parameterized types cannot be declared. For example:
Pair<String>[] table = newPair<String>(10); //ERROR
This is because after erasure, the table Type becomes pair [] and can be converted into an object [].
Object[] objarray =table;
The array can remember its own element type. The following assignment will throw an arraystoreexception.
objarray ="Hello"; //ERROR
For generics, erasure reduces the efficiency of this mechanism. The values below can be checked by array storage, but they still result in type errors.
objarray =new Pair<Employee>();
Tip: To collect parameterized objects, use arraylist: arraylist <pair <string> to guarantee the security and effectiveness.
8. instantiation of generic types
The generic type cannot be instantiated. For example,
first = new T(); //ERROR
Yes. Type erasure will make this operation new object ().
You cannot create a Generic Array.
public<T> T[] minMax(T[] a){ T[] mm = new T[2]; //ERROR ... }
Similarly, erasure will make this method always rely on an object [2] array. However, you can use reflection to construct generic objects and arrays.
Use reflection to call array. newinstance:
Publicstatic <t extends comparable> T [] MINMAX (T [] A) {T [] Mm = (T []) array. newinstance (. getclass (). getcomponenttype (), 2 );... // Replace the following code // obeject [] Mm = new object [2]; // return (T []) mm ;}
9. Type erasure conflict
1,
When the generic type is erased, the creation conditions cannot conflict. If the following equals method is added to the pair class:
class Pair<T> {public boolean equals(T value) {return null;}}
Consider a pair <string>. Conceptually, it has two equals methods:
Booleanequals (string); // defined in pair <t>
Boolean equals (object); // inherits from the object
However, this is just an illusion. Actually, the erased Method
Boolean equals (t)
Changed to the Boolean equals (object) method)
This conflicts with the object. Equals method! Of course, the remedy is to rename and cause an error.
2,
The generic specification mentions another principle: "To support erasure conversion, you must forcibly create a class or type variable and cannot be a subclass of two interfaces at the same time, these two subclasses are different parameterization of the same product."
The following code is invalid:
class Calendar implements Comparable<Calendar>{ ... }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //ERROR
Gregoriancalendar implements comparable <calender> and compable <gregoriancalendar>, which are different parameterization implementations of the same interface.
The relationship between this restriction and type erasure is not clear. Non-generic version:
class Calendar implements Comparable{ ... }
class GregorianCalendar extends Calendar implements Comparable{...} //ERROR
It is legal.
10. Problems of generics in static methods and static classes
Static methods and static variables in generic classes cannot use the generic parameters declared by generic classes.
Example:
Public class Test2 <t> {public static t one; // compilation error public static t show (T one) {// compilation error return NULL ;}}
Because the instantiation of generic parameters in a generic class is specified when an object is defined, static variables and static methods do not need to be called using objects. The object is not created. It is of course incorrect to determine the type of the generic parameter.
However, pay attention to the following situations:
Public class Test2 <t> {public static <t> T show (T one) {// This is the correct return NULL ;}}
This is a generic method. In a generic method, t is defined in the method rather than T in the generic class.