Java magic Hall: Interpreting generic Type based on Type Erasure

Source: Internet
Author: User

Java magic Hall: Interpreting generic Type based on Type Erasure
I. Preface Do you still remember the bitterness of traversing the list during JDK1.4? I still remember it. At that time, due to project requirements, I turned from C # to Java, and then because JDK1.4 was missing the syntax sugar such as generics (as well as automatic packing and unpacking ), I have suffered a lot, but it also reflects that my level still needs to be improved. JDK1.5 introduces features such as generic and automatic packing and unpacking, and the transition from C # to Java is smooth. Next, let's review the difference between non-generic and generic types! // List of non-generic traversal lists lst = new ArrayList (); lst. add (1); lst. add (3); int sum = 0; for (Iterator = lst. iterator (); lst. hasNext ();) {Integer I = (Integer) lst. next (); sum + = I. intValue () ;}// List of generic traversal lists <Integer> lst = new ArrayList <Integer> (); lst. add (1); lst. add (3); int sum = 0; for (Iterator = lst. iterator (); lst. hasNext ();) {Integer I = lst. next (); sum + = I;} the main function of the generic type is to check the type of the Set element during compilation, rather than throw ClassCastException during runtime. Note: The following content is based on JDK 7 and HotSpot. 2. Before introducing generics, define two test classes: Class P and class S extends P. 1. declare generic variables, such as List <String> lst = new ArrayList <String> (). Note: Generic variables do not support covariant. // S is a subclass of P, however, List <S> is not a subclass of List <P>, that is, the following statement cannot be compiled by compiling List <P> lst = new ArrayList <S> (); // The array supports covariant P [] array = new S [10]. Note that the parent class is used as the type parameter, the subclass instance can be used as the set element List <P> lst = new ArrayList <P> (); lst. add (new S (); 2. declare wildcard variables, such as List <?> Lst = new ArrayList <P> (); wildcard? Indicates that the type parameter is of unknown type, so any type parameter can be assigned to it. When the set type parameter? Cannot add other types of instances except null to the set. (Null is a subclass of all classes, so it can be assigned to an unknown type.) List <?> Lst = new ArrayList <P> (); lst = new ArrayList <S> (); // The following statement will cause compilation failure lst. add (new S (); // The following sentence is OKlst. add (null); therefore, wildcard variables are generally used to retrieve and traverse collection elements without adding elements. Void read (List <?> Lst) {for (Object o: lst) {System. out. println (o. toString () ;}list <String> lst = new ArrayList <String> (); lst. add ("1"); lst. add ("2"); read (lst); Here you will find that the wildcard set (unbounded wildcard generic type) and the non-generic set (raw type) are used) the results are the same, but they are not. we can add any type of elements to a non-generic set, while the wildcard set can only add null, thus improving the type security. we can also use a wildcard set with boundary wildcards with restrictions! 3. Declare a wildcard with a boundary? Extends generic variables, such as List <? Extends P> lst = new ArrayList <S> (); what is the boundary wildcard? Extends limits that the actual type parameter must be the specified class or its subclass before compilation. Void read (List <? Extends P> lst) {for (P p: lst) {System. out. println (p) ;}}list <P> lst = new ArrayList <P> (); lst. add (new P (); lst. add (new S (); read (lst); 4. declare a wildcard with a boundary? Super generic variables, such as List <? Super S> lst = new ArrayList <P> (); what is the boundary wildcard? Super limits that the actual type parameter must be the specified class itself or its parent class to be compiled. Note: The type of the Set element must be the specified class or its subclass. Void read (List <? Super S> lst) {for (S s: lst) System. out. println (s);} List <P> lst = new ArrayList <P> (); lst. add (new S (); read (lst); 5. define generic classes or interfaces, such as class Fruit <T >{} and interface Fruit <T >{} T, which are placeholders of type parameters and are generally named with a single uppercase letter. The following is a recommended placeholder name: K -- Key, such as the ing key. V -- value, such as List and Set content, value E in Map -- exception class T -- generic except exception class, enumeration, and anonymous internal class, other classes or interfaces can be defined as generic classes. The type parameters of a generic class can be used in instance methods, instance fields, and constructors. They cannot be used in class methods, class fields, or static code blocks. Class Fruit <T> {// The type parameter placeholder serves as the instance field type private T fruit; // The type parameter placeholder serves as the return value type of the instance method T getFruit () {return fruit ;} // The type parameter placeholder acts as the input parameter type void setFruit (T fruit) {this. fruit = fruit;} private List <T> fruits; // type parameter placeholder as the restriction condition of the boundary wildcard void setFruits (List <? Extends T> lst) {fruits = (List <T>) lst ;} // The type parameter placeholder is used as the input parameter type of the instance method. void setFruits2 (List <T> lst) {fruits = lst ;} // The constructor does not need to include a generic Fruit () {// type parameter placeholder as the type of the local variable fruits = new ArrayList <T> (); T fruit = null ;}} and the boundary wildcard. Generally, the type parameter placeholders can also carry boundaries, such as class Fruit <T extends P> {}. When there are multiple constraints on the relationship, use & to connect multiple parent classes, such as class Fruit <T extends A & B & C & D> {}. You can also define multiple types of parameter placeholders, such as class Fruit <S, T >{}, class Fruit <S, T extends A >{}, etc. The following describes the problem of inheriting generic classes or interfaces. Assume that classes with generic class P are defined as class P <T> {}, so when inheriting class P, we have two options: 1. type parameter of the specified class P 2. inherit the type parameter of class P // 1. specify the type parameter of the parent class S extends P <String >{} // 2. inherit the type parameter class S <T> extends P <T >{} 6 of the parent class. use generic classes or interfaces, such as Fruit <?> Fruit = new Fruit <Apple> (); now the problem arises. If the Fruit class is defined as follows: public class Fruit <T extends P >{}, it is assumed that the usage is Fruit <? Extends String> fruit; are you sure you can pass the compilation? The answer is no. The type parameter has been limited to a subclass of P or P, so only Fruit <? Extends P> or Fruit <? Extends S> can be compiled. 7. Defining generic methods can be defined as generic methods, including instance methods, class methods, and abstract methods. // The instance method public <T> void say (T [] msgs) {for (T msg: msgs) System. out. println (msg. toString ();} public <T extends P> T create (Class <T> clazz) throws InstantiationException, IllegalAccessException {return clazz. newInstance () ;}// class method public static <T> void say (T msg) {System. out. println (msg. toString ();} public static <T extends P> T create (Class <T> clazz) throws InstantiationException, IllegalAccessExc Eption {return clazz. newInstance () ;}// abstract method public abstract <T> void say (T msg); public abstract <T extends P> T create (Class <T> clazz) throws InstantiationException, illegalAccessException {} 8. the generic method can be used to implicitly specify the actual type or explicitly specify the actual type. P p = new P (); String msg = "Hello"; // implicitly specify the actual type p. say (msg); // explicitly specify the actual type p. <String> say (msg); Generally, you can use the implicit method to specify the actual type. 9. Only wildcard characters can be used to create a List of generic arrays. <?> [] Lsa = new ArrayList <String> [10]; // throw an exception List <?> [] Lsa = new ArrayList <?> [10]; List <String> list = new ArrayList <String> (); list. add ("test"); lsa [0] = list; System. out. println (lsa [0]. get (0); 4. Type Erasure and Code Bloat. Now everyone has a certain understanding of Java generics, however, some strange things happen from time to time during the application. Before introducing these strange cases, we need to add some basic knowledge, that is, how Java implements generics. There are two ways to implement generics: Code Specialization: a new target Code (bytecode or binary Code) is generated when a generic class or generic method is instantiated ). For example, for a generic List, when List <String> and List <Integer> appear in the program, Class instances such as List <String> and List <Integer> are generated. 2. code Sharing: Only one unique target Code is generated for each generic class. The data types of all instances of this generic class are mapped to this target Code, perform type check and type conversion as needed. For example, only a List <Object> Class instance is generated for List <String> and List <Integer>. C ++ templates and C # are typical Code Specialization. If N types of L generic List appear in the program, N Class instances are generated, which causes Code Bloat ). Java adopts the Code Sharing idea and implements it through Type Erasure. The process of Type erasure is roughly divided into two steps: ①. Use the boundary type of the generic parameter extends to replace the generic parameter (<T> default is <T extends Object>, <?> The default value is <? Extends Object> ). ②. Insert the type check and type conversion statements at the desired position. Interface Comparable <T> {int compareTo (T that);} after the final class NumericVal implements Comparable <NumericVal> {public int compareTo (NumericVal that) {return 1 ;}} is erased: interface Comparable {int compareTo (Object that);} final class NumericVal implements Comparable {public int compareTo (NumericVal that) {return 1 ;} // The Compiler automatically generates public int compareTo (Object that) {return this. compareTo (NumbericVal) that) ;}} also That is, List <String> lstStr = new ArrayList <String> (); List <Integer> intStr = new ArrayList <Integer> (); System. out. println (lstStr. getClass () = intStr. getClas (); // display true, because the types of lstStr and intStr are erased as List. 5. Various types of generic types based on Type Erasure. 1. generic shared class variable class Fruit <T> {static String price = 0;} Fruit <Apple>. price = 12; Fruit <Pear>. price = 5; System. out. println (Fruit. <Apple>. price); // output 5 2. the placeholder of instanceof type parameter throws a compilation exception. List <String> strLst = new ArrayList <String> (); if (strLst instanceof List <String>) {}// if (strLst instanceof List) is not compiled) {} // compile 3. new Type parameter placeholder throws compilation exception class P <T> {T val = new T (); // does not pass compilation} 4. define generic Exception classes to throw compilation Exception class MyException <T> extends Exception {}// do not pass compilation 5. different generic parameters cannot be used as different descriptor identifiers to distinguish methods. // they are treated as the same method. Therefore, a public void say (List <String> msg) conflict occurs) {} public void say (List <Integer> number) {} // After JDK6, you can use different return value classes. Resolve the conflict // for Java, the method signature is only for the method name + parameter list, but for Bytecodes, the method signature also contains the return value type. Therefore, in this special case, the Java compiler allows this processing method to be public void say (List <String> msg) {} public int say (List <Integer> number) {} 6. Go into step 1. how does one determine the actual type of calling a generic method by implicitly specifying the type parameter type? Assume that the existing generic method is defined as <T extends Number> T handle (T arg1, T arg2) {return arg1;}. Then, based on the Operation steps of Type erasure, the actual type of T must be Number. Let's look at the bytecode Method handle :( Ljava/lang/Number;) Ljava/lang/Number; the rest is the activity of type check and type conversion. Different input parameters and combinations of type conversion for return values will lead to different results. // Integer ret = handle (1, 1L); // Number ret = handle (1, 1L) compiled successfully ); integer ret = handle (); Number ret = handle (1, 1L) the corresponding Bytecodes is 14: invokestatic #4 // Method java/lang/Integer. valueOf :( I) Ljava/lang/Integer; 17: invokevirtual #5 // Method handle :( Ljava/lang/Number;) Ljava/lang/Number; while the Bytescodes corresponding to Interger ret = handle (1, 1L) has the checkcast command for type conversion 14: invokestatic #4 // Method java/lang/Integer. valueOf :( I) Ljava/lang/Integer; 17: invokevirtual #5 // Method handle :( Ljava/lang/Number;) Ljava/lang/Number; 20: checkcast #6 // class java/lang/Integer according to the above rules, therefore, the following code fails to compile due to a method definition conflict. // compilation fails. <T extends String> void println (T msg) {} void println (String msg) {} 2. two generic methods with the same effect but different expressions: public static <T extends P> T getP1 (Class <T> clazz) {T ret = null; try {ret = Clazz. newInstance ();} catch (InstantiationException | IllegalAccessException e) {} return ret ;}} public static <T> T getP2 (Class <? Extends P> clazz) {T ret = null; try {ret = (T) clazz. newInstance ();} catch (InstantiationException | IllegalAccessException e) {} return ret ;}} the content of getP1 is not hard to understand. The placeholder T of the type parameter will be compiled into P, therefore, the code after type erasure is: public static P getP1 (Class clazz) {P ret = null; try {ret = (P) clazz. newInstance ();} catch (InstantiationException | IllegalAccessException e) {} return ret ;}} while T in getP2 is compiled as Object, clazz. the newInstance () returned value type is Object. Why? Add (T) for explicit type conversion? But if you change <T> to <T extends Number>, the explicit type conversion will become a product. I guess it is because of the Writing Method of getP2 that there is no association between the type parameters of the returned values and the input parameters, and it cannot be guaranteed that the implicit type conversion can be successfully executed, therefore, developers must perform explicit type conversion; otherwise, compilation fails. But the biggest concern is that there is no type conversion statement in Bytecodes 3: invokevirtual #2 // Method java/lang/Class. newInstance :() Ljava/lang/Object; 6: astore_1

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.