Generic erasure and generic information acquisition in Java
Generic erasure of Java
Erase
Class c1 = new ArrayList<Integer>().getClass(); Class c2 = new ArrayList<String>().getClass(); (c1 == c2); /* Output true */
ArrayList<Integer> and ArrayList<String> are completely different types when compiled. You cannot add an instance of type String to ArrayList<Integer> while writing code. However, when the program is running, it will indeed output true.
This is caused by type erasure of Java generics, because whether it is ArrayList<Integer> or ArrayList<String>, it will be erased by the compiler to an ArrayList at compile time. Java avoids creating new classes when creating generic instances, thereby avoiding excessive consumption during runtime.
List<Integer> list = new ArrayList<Integer>(); Map<Integer, String> map = new HashMap<Integer, String>(); ((().getTypeParameters())); ((().getTypeParameters())); /* Output [E] [K, V] */
We may expect to be able to get real generic parameters, but only get the generic parameter placeholder when declaring. The Javadoc of the getTypeParameters method is also explained in this way: only the generic parameters at the time of declaration are returned. Therefore, the runtime generic information cannot be obtained through the getTypeParamters method.
Erase to upper limit
class A<T extends Number> { }
Use javap -v to see generic signatures
Signature: #18 // <T:Ljava/lang/Number;>Ljava/lang/Object;
Convert
Originally a has generic information, but b does not, so the generic information is lost during the assignment process, and the type of T in b will become its upper limit Number
//In the following code, when an instance of a with generic information is assigned to a b without generic information,//A all generic information in a will be lost, that is to say, the information that T is Integer will be lost, b only knows//T type is just Numberpackage ErasureAndConversion; import UseIt.A1; class Apple<T extends Number>{ T size; public Apple(){ } public Apple(T size){ = size; } public void setSize(T size){ = size; } public T getSize(){ return ; } } public class Erasure { public static void main(String args[]){ Apple<Integer> a = new Apple<>(6); // The instance pointed to by a is with generic information Integer as = (); // T in instance a is of Integer type, so there is no problem with assigning value to as Apple b = a; // This sentence is the key to explaining the problem. B does not contain generic information, so the generic information in a// It will also be erased, so the type of T in a is just Number Number size1 = (); // The T type in b is Number, so there is no problem assigning value to the value of the Number type // Integer size2 = (); // However, T in b is not from Integer, because the generic information has been erased, so this sentence will// An error was reported. Integer size3 = (); // This last sentence will not report an error. B will lose generic information, but a will not be affected. } }
The following two examples illustrate the same problem, or the second example is the intuitive manifestation of the first example. It means that when a set with generic information is assigned to a set without generic information, the generic information is lost, so there will be no problem when assigning a list to ls, because the list no longer knows what the specific generic information is, so it is an Object, so it can be assigned to ls, but once you want to access elements in the collection, type mismatch will occur.
//java allows to assign a List to a List<Type> so below List<String> ls = list;//This sentence will only cause a warning.package ErasureAndConversion; import ; import ; public class Erasure2 { public static void main(String args[]){ List<Integer> li = new ArrayList<>(); (6); (5); List list = li; List<String> ls = list; // The same goes for this reason, List has no generic information, so the generic information of li is lost, so the value is assigned to ls// There is no problem // ((0)); // But when accessing elements in ls, type mismatch will occur } }
//This example is exactly the same as the above examplepackage ErasureAndConversion; import ; import ; public class Erasure3 { public static void main(String args[]){ List list = new ArrayList(); ((ArrayList) list).add(5); ((ArrayList) list).add(4); // ((String)(0)); } }
Getting generic information
Inherit a generic base class
class A<T, ID> { } class B extends A<String, Integer> { } public class Generic { public static void main(String[] args) { ParameterizedType parameterizedType = (ParameterizedType) (); Type[] actualTypeArguments = (); for(Type actualTypeArgument: actualTypeArguments) { (actualTypeArgument); } Class clazz = (Class) ()[0]; (clazz); } }
The above code outputs:
class
class
class
Implement a generic interface
interface A<T, ID> { } class B implements A<String, Integer> { } public class Generic { public static void main(String[] args) { ParameterizedType parameterizedType = (ParameterizedType) (); Type[] actualTypeArguments = (); for (Type actualTypeArgument : actualTypeArguments) { (actualTypeArgument); } Class clazz = (Class) ()[0]; (clazz); } }
The same result can be obtained as above.
Getting generic information at runtime (illusion, essentially by defining classes)
Introduce some little knowledge
Anonymous internal class:
- Concept: simplified writing of internal classes
- Prerequisite: There is a class (can be a concrete class or an abstract class) or an interface
- Format: new class name or interface name {rewrite method}
- Essence: Create an anonymous object that inherits the class or implements the interface.
Anonymous class declaration:
- The declaration of anonymous class is automatically derived from a class instance to create expressions by the java compiler;
- Anonymous classes can never be abstract;
- Anonymous classes are always implicitly final;
- Anonymous classes are always an inner class and cannot be static;
Due to the implementation mechanism of Java generics, the types of generic parameters related to the code that uses generics will be erased during the run. We cannot know the specific type of generic parameters during the run (all generic types are Object types at runtime), but the information related to generics is still saved in the compilation of the java source code into a class file. This information is saved in the class bytecode constant pool. A signature signature field will be generated at the code that uses generics, and the address of this constant pool is indicated by the signature signature field.
The reason why Java introduces generic erasure is to avoid the creation of unnecessary classes at runtime due to the introduction of generics. Through the previous knowledge, we can actually retain generic information in class information by defining classes, thereby obtaining these generic information. In short, Java generic erasure is scoped, that is, generics in class definitions will not be erased.
In some scenarios, we need to obtain generic information. For example, when calling the HTTP or RPC interface, we need to serialize and deserialize.
For example, we receive the following JSON data through an HTTP interface
[{ "name": "Stark", "nickName": "Iron Man" }, { "name": "Rogers", "nickName": "Captain America" }]
We need to map it to List<Avenger>.
But we mentioned generic erasure before, so how do we use HTTP or RPC frameworks to obtain generic information in List?
The following code
Map<String, Integer> map = new HashMap<String, Integer>() {}; Type type = ().getGenericSuperclass(); ParameterizedType parameterizedType = (type); //ParameterizedType parameterizedType = (ParameterizedType)().getGenericSuperclass(); for (Type typeArgument : ()) { (()); } /* Output */
The above code shows how to obtain the generic information of the corresponding class to the map instance. Obviously, this time we successfully obtained the generic parameter information. With these generic parameters, the serialization and deserialization work mentioned above is possible.
Then why can't it be before, but this time it can be? Please note a detail
The previous variable declaration
Map<Integer, String> map = new HashMap<Integer, String>();
Variable declarations in this section
Map<String, Integer> map = new HashMap<String, Integer>() {};
The most critical difference is that the variable declaration in this section has a pair of braces, which actually creates an anonymous inner class. This class is a subclass of HashMap, and the generic parameters are limited to String and Integer, so that the generic information is retained by defining the class.
Applications in the framework
In fact, many frameworks use generics in class definitions to not be erased, and implement corresponding functions.
For example, SpringWeb module RestTemplate and Alibaba's fastJson, we can use the following writing method:
//The ParameterizedTypeReference here is an abstract class, so it restricts the subclass of ParameterizedTypeReference that must be created, thereby successfully obtaining the actual type of the genericResponseEntity<ResponseDTO<UserKeyDTO>> result = (url, null, new ParameterizedTypeReference<ResponseDTO<UserKeyDTO>>(){}); //Remain the introspection information by creating anonymous inner class of TypeReference so that the actual generic type can be reflected when deserializing jsonResponseDTO<SysCryptDTO> responseDTO = (jsonString, new TypeReference<ResponseDTO<SysCryptDTO>>() {});
The new ParameterizedTypeReference<YourType>() {} is to obtain generic information by defining an anonymous inner class, thereby deserializing it.
Summarize
Java generic erasure is an important feature of Java generics. Its purpose is to avoid excessive runtime consumption caused by excessive creation of classes. So, want to use ArrayList<Integer> and ArrayList<String> to have the same class instance.
But in many cases we need to obtain generic information at runtime, so we can obtain generic parameters at runtime by defining classes (usually anonymous internal class, because we create this class just to obtain generic information), so as to meet the needs of work such as serialization, deserialization, etc.
As long as you understand the reasons why Java introduces generic erasure, you can naturally understand how to obtain generic information at runtime.
The above is personal experience. I hope you can give you a reference and I hope you can support me more.