Contents of Spring Framework-IoC Series Articles
Generics are widely used in a variety of frameworks. They have a high exposure in everyday development or source reading, and it is necessary to understand them. Generic basic usage scenarios are relatively simple, but are often not easy to understand in complex scenarios
This paper first introduces the basic concepts of java generics, then lists some complex scenarios for using generics. Finally, it introduces the powerful and easy-to-use generic API in spring, hoping to help you understand generics more deeply, or make better use of them for common design in your daily development work.
Let's start with the java generics basics: Generics allow users to use type(class, interface) as a type parameter when defining classes, interfaces, and methods. As with method parameters, once a type parameter is defined, it can be referenced in the corresponding scope (class, interface, method). The difference is that the input to the method parameter is a value, and the input to the type parameter is a type
The generic terms involved in this passage explain:
Generic types: Include generic classes and generic interfaces, define formats: class name < T1, T2,..., Tn> {}
Generic method: Define format: <T1, T2,..., Tn> void test(T1 t1,T2 t2...) {}, both static and non-static methods support generics, note that generic parameters must precede method return types
Type parameter/type variable: T1, T2 included in'<>'in a generic type or method declaration. Is the type parameter (also known as type variable)
In addition, several terms used below are also explained here:
Parameterized type: Foo<String>is parameterized type
Type argument: Be careful not to be confused with type parameter, which is a parameter defined at the time of declaration, and type argument, which is an argument (the actual parameter passed in). For example, T in class Foo <T>{} is a type parameter, while String in Foo <String> is an argument.
Raw type: parameterized type is the original type after removing the type argument, and Foo in Foo<String>is the raw type
We won't expand on the basic usage scenarios for generics, but here are some slightly more complex scenarios
Scenario 1:
public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK List<Number> list = new ArrayList<Number>(); list.add(new Integer(10)); // OK list.add(new Double(10.1)); // OK
There is no doubt that both scenarios are OK because Integer and Double are subtypes of Number, so let's consider what type of parameters can be passed in by this method:
public void test(List<Number> n) {}
Is List <Integer>, List <Double> OK? The answer is no, because List <Integer>, List <Double> are not subclasses of List <Number>, ArrayList <Number>, LinkedList <Number> are possible, they are subclasses of List <Number>
Scenario 2:
//Define a generic interface A interface A<T>{}
You must have encountered each of these implementations:
//Mode 1 class B implements A<String>{} //Mode 2 class B<T> implements A<T>{} //Mode 3 class B<T extends User> implements A<T>{} //Mode 4 class B<T> implements A<String>{} //Mode 5 class B<T,E> implements A<T>{}
So what do they mean?
Mode 1: Subclass B specifies type argument String, at which point B is no longer a generic class, and when B is used, type argumentation is no longer required
Mode 2: Subclass B inherits the parent's type parameter intact, and when using B, any type argument can be specified
Mode 3: Subclass B redefines the type parameter inherited from the parent class, narrowing it down to be a User and its descendants. When using B, only type argument of User type (including descendants of User) can be specified.
Mode 4: Subclass B specifies the type argument from the parent class as a String and defines its own type parameter(T), which has nothing to do with the parent class
Mode 5: Subclass B inherits the type parameter(T) of the parent class and defines its own type parameter(E). When using B, two type argument s need to be specified
Scenario three:
The handle method of abstract template class A first cascades the input json into a T-type object, then handles it to the subclass
public abstract class A<T>{ public void handle(String json){ //Convert json to a T-type object and pass it to the subclass T t=fromJson(json); doHandle(t); } private T fromJson(String json) { //How? } protected abstract void doHandle(T t); }
Consider how the fromJson method is implemented. When a json becomes an object, it must first get the corresponding Class. How? T.class Guess you can't?.. The correct solution is as follows, which you can interpret in conjunction with the above NOUN explanations:
public abstract class A<T>{ public void handle(String json){ T t=fromJson(json); doHandle(t); } private T fromJson(String json) { //Gets the parameterized type of the parent class, A<User>. Explain "parameterized type" for corresponding nouns ParameterizedType parameterizedType=(ParameterizedType)this.getClass().getGenericSuperclass(); //Gets its type arguments new Type[]{User.class}. Explain "type argument" with corresponding nouns Type[] typeArguments=parameterizedType.getActualTypeArguments(); //Get the first type argument, User.class Class<T> clazzT=(Class<T>)typeArguments[0]; return new Gson().fromJson(json,clazzT); } protected abstract void doHandle(T t); } public class B extends A<User>{ @Override protected void doHandle(User user) { //do something } }
Scenario 4:
Class C<T>{}, how do I get the corresponding type argument for T in C? The answer is that there is no way to get it. Scenario 3 is because type argumentation was determined during compilation (class B extends A<User>). In the current scenario, only type parameter has no type argument at compilation time, and processing for generics is done during compilation. It is not feasible to try to get generic information from runtime
Supplementary Notes:
jdk1.5 defines java.lang.reflect.Type, which is the common parent interface for all types in java, includes:
Class: Basic data type (int/short/boolean..) All running class and interface types, such as array type, enumeration type, are represented in Class
ParameterizedType: For example, MyService<User>
Type parameter/type variable: such as T
Generic array types, such as T[]
Spring provides a powerful generic parsing tool, ResolvableType: ResolvableType encapsulates a java. Lang.reflect. A Type object that provides access to information on a Type, such as the parent class (getSuperType()), the interface (getInterfaces()), and the generic parameters on a class (getGeneric(int...)). And the ability to resolve into classes (resolve())
ResolvableType can be constructed by field, method parameter, method return type, or class, corresponding to the static methods in ResolvableType: forField, forMethodParameter(Method, int), forMethodReturnType(Method), forClass(Class)
Here are a few examples of ResolvableType's capabilities and usability
HashMap<Integer, List<String>> myMap; public void example1() throws NoSuchFieldException { //Construct a ResolvableType object from field ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap")); //Get the parent class and resolve to class->java. Util. AbstractMap System.out.println(t.getSuperType().resolve()); //Get the first type argument and parse it into class->java. Lang.Integer System.out.println(t.getGeneric(0).resolve()); //Ditto System.out.println(t.resolveGeneric(0)); //Get the second type argument and parse it into class->java. Util. List, note: resolve returns raw type if type argument is still a generic type System.out.println(t.getGeneric(1).resolve()); //Ditto System.out.println(t.resolveGeneric(1)); //Get the first type argument of the list and resolve to class->java. Lang.String System.out.println(t.getGeneric(1).getGeneric(0).resolve()); //Ditto System.out.println(t.resolveGeneric(1, 0)); } //================================================================ public Map<String, List<Integer>> test(){return null;} void example2() throws NoSuchMethodException { //Construct a ResolvableType object with method return type ResolvableType methodReturnType=ResolvableType.forMethodReturnType(getClass().getMethod("test")); //->java.lang.String System.out.println(methodReturnType.resolveGeneric(0)); //->java.util.List System.out.println(methodReturnType.resolveGeneric(1)); //->java.lang.Integer System.out.println(methodReturnType.resolveGeneric(1, 0)); } //================================================================ //This is an example from the previous one, which is implemented using ResolvableType, and you can compare it with the previous implementation using jdk's own api public abstract class A<T>{ public void handle(String json){ T t=fromJson(json); doHandle(t); } private T fromJson(String json) { //Construct a ResolvableType object through class and return it as the parent ResolvableType ResolvableType classResolvableType=ResolvableType.forClass(this.getClass()).as(A.class); //->User Class<T> clazzT=classResolvableType.resolveGeneric(0); return new Gson().fromJson(json,clazzT); } protected abstract void doHandle(T t); } public class B extends A<User>{ @Override protected void doHandle(User user) { //do something } }
Almost as you can imagine generic scenarios, ResolvableType can help you solve them, but more importantly, the solutions are all elegant, I hope you can master them