Java充电社
专辑
博文
联系我
本人继续续收门徒,亲手指导
Java泛型专题
-> 万字长文详解泛型解析
1、万字长文详解Java泛型
2、聊一聊-JAVA 泛型中的通配符 T,E,K,V,?
3、万字长文详解泛型解析
上一篇:聊一聊-JAVA 泛型中的通配符 T,E,K,V,?
<div style="display:none"></div> ##目录 [TOC] 本文主要讲解泛型类型的解析,泛型算是必须要掌握的一块硬核知识,在很多地方都会用到,这块如果理解了,在阅读其他框架源码的时候会让你更容易一些,看完本文之后大家对泛型也有一个新的认识。 关于泛型的解析上面,我们需要先了解一些类和接口,这些比较关键,这些都位于java.lang.reflect包中,类图如下: ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/209/2ca0f3bd-ff91-414f-bee3-16d8b8c2a2eb.png) 下面一个个来解释。 ## 1. Type接口 这是一个顶层接口,java中的任何类型都可以用这个来表示,这个接口是Java编程语言中所有类型的公共超接口。这些类型包括原始类型、泛型类型、泛型变量类型、通配符类型、泛型数组类型、数组类型等各种类型。 这个接口代码比较简单,源码: ```java public interface Type { /** * Returns a string describing this type, including information * about any type parameters. * * @implSpec The default implementation calls {@code toString}. * * @return a string describing this type * @since 1.8 */ default String getTypeName() { return toString(); } } ``` 这个接口只有一个方法`getTypeName`,用于返回具体类型的名称,是一个默认方法,默认会调用当前类的toString方法,实现类也可以对这个方法重写。 ## 2. GenericDeclaration接口 所有声明泛型变量的公共接口,这个接口中定义了一个方法: ```java public TypeVariable<?>[] getTypeParameters() ``` 这个方法用于获取声明的泛型类型变量清单。 泛型变量可以在类和方法中进行声明,从上面类图中也可以看出来,java中任何类可以使用Class对象表示,方法可以用Method类表示,类图中可以知,Class类和Method类实现了GenericDeclaration接口,所以可以调用他们的`getTypeParameters`方法获取其声明的泛型参数列表。 ### 2.1. 类中定义泛型变量类型 ```java public class Demo1<T1, T2 extends Integer, T3 extends Demo1I1 & Demo1I2> ``` 上面代码表示Demo1这个类中声明了3个泛型变量类型:T1、T2、T3,所以如果去调用这个类的Clas对象中的getTypeParameters方法可以获取到这三个泛型变量的信息,文章后面有案例演示。 ### 2.2. 方法中定义泛型变量类型 ```java public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s) { return t3; } ``` 上面m1方法中声明了三个泛型类型变量:T1、T2、T3;java中可以方法的任何信息都可以通过Method对象来获取,Mehod类实现了GenericDeclaration接口,所以Method类中实现了GenericDeclaration接口中的getTypeParameters方法,调用这个方法就可以获取m1方法中3个泛型变量类型的信息,文章后面有案例演示。 ## 3. Class类 这个比较常见,Class类的对象表示JVM中一个类或者接口,每个java对象被加载到jvm中都会表现为一个Class类型的对象,java中的数组也被映射为Class对象,所有元素类型相同且维数相同的数组都共享一个class对象,通过Class对象可以获取类或者接口中的任何信息,比如:类名、类中声明的泛型信息、类的修饰符、类的父类信息、类的接口信息、类中的任何方法信息、类中任何字段信息等等。 ### 3.1. Class对象获取方式 在程序中我们可以通过3中方式获取Class对象: ```java 1.类名.class 2.对象.getClass() 3.Class.forName("类或者接口的完整名称") ``` ### 3.2. 常用的方法 #### 3.2.1. Field[] getFields() 这个方法会返回当前类的以及其所有父类、父类的父类中所有public类型的字段。 #### 3.2.2. Field[] getDeclaredFields() 这个方法会返回当前类中所有字段(和修饰符无关),也就说不管这个字段是public还是private或者是protected,都会返回,有一点需要注意,只返回自己内部定义的字段,不包含其父类中的,这点需要注意,和getFields是有区别的。 #### 3.2.3. Method[] getMethods() 这个方法会返回当前类的以及其所有父类的、父类的父类的、自己实现的接口、父接口继承的接口中的所有public类型的方法,需要注意一下,接口中的方法默认都是public类型的,接口中的方法public修饰符是可以省略的。 #### 3.2.4. Method[] getDeclaredMethods() 返回当前类中定义的所有方法,不管这个方法修饰符是什么类型的,注意只包含自己内部定义的方法,不包含当前类的父类或者其实现的接口中定义的。 #### 3.2.5. Type getGenericSuperclass() 返回父类的类型信息,如果父类是泛型类型,会返回超类中泛型的详细信息,这个方法比较关键,后面会有详细案例。 #### 3.2.6. TypeVariable<Class<T>>[] getTypeParameters() Class类继承了`java.lang.reflect.GenericDeclaration`接口,上面这个方法是在GenericDeclaration接口中定义的,Class类中实现了这个接口,用于返回当前类中声明的泛型变量参数列表。 ## 4. Method类 这个类用来表示java中的任何一个方法,通过这个类可以获取java中方法的任何信息,比如:方法的修饰符、方法名称、方法的参数、方法返回值、方法中声明的泛型参数列表等方法的一切信息。 ### 4.1. 常用的方法 #### 4.1.1. String getName() 用来获取方法的名称。 #### 4.1.2. Type[] getGenericParameterTypes() 返回方法的参数信息,如果参数是泛型类型的,会返回泛型的详细信息,这个方法后面会演示。 #### 4.1.3. Type getGenericReturnType() 返回方法的返回值类型,如果返回值是泛型的,会包含泛型的详细信息。 #### 4.1.4. TypeVariable<Method>[] getTypeParameters() Method类继承了`java.lang.reflect.GenericDeclaration`接口,上面这个方法是在GenericDeclaration接口中定义的,Method类中实现了这个接口,用于返回当前方法中声明的泛型变量参数列表。 ## 5. Field类 这个类用来表示java中的字段,通过这个类可以获取java中字段的任何信息,比如:字段的修饰符、字段名称、字段类型、泛型字段的类型等字段的一切信息。 ### 5.1. 常用的方法 #### 5.1.1. String getName() 获取字段的名称。 #### 5.1.2. Class<?> getType() 获取字段类型所属的Class对象。 #### 5.1.3. Type getGenericType() 获取字段的类型,如果字段是泛型类型的,会返回泛型类型的详细信息;如果字段不是泛型类型的,和getType返回的结果是一样的。 #### 5.1.4. Class<?> getDeclaringClass() 获取这个字段是在哪个类中声明的,也就是当前字段所属的类。 ## 6. ParameterizedType接口 这个接口表示参数化类型,例如List<String>、Map<Integer,String>、UserMapper<UserModel>这种带有泛型的类型。 ### 6.1. 常用方法 这个接口中定义了3个方法,都比较重要,来看一下。 #### 6.1.1. Type[] getActualTypeArguments() 获取泛型类型中的类型列表,就是<>中包含的参数列表,如:List<String>泛型类型列表只有一个是String,而Map<Integer,String>泛型类型中包含2个类型:Integer和String,UserMapper<UserModel>泛型类型为UserModel,实际上就是<和>中间包含的类型列表。 #### 6.1.2. Type getRawType() 返回参数化类型中的原始类型,比如:List<String>的原始类型为List,UserMapper<UserModel>原始类型为UserMapper,也就是<符号前面的部分。 #### 6.1.3. Type[] getOwnerType() 返回当前类型所属的类型。例如存在A<T>类,其中定义了内部类InnerA<T>,则InnerA<T>所属的类型为A<T>,如果是顶层类型则返回null。这种关系比较常见的示例是Map<K,V>接口与Map.Entry<K,V>接口,Map<K,V>接口是Map.Entry<K,V>接口的所有者。 ## 7. TypeVariable接口 这个接口表示的是泛型变量,例如:List<T>中的T就是类型变量;而class C1<T1,T2,T3>{}表示一个类,这个类中定义了3个泛型变量类型,分别是T1、T2和T2,泛型变量在java中使用TypeVariable接口来表示,可以通过这个接口提供的方法获取泛型变量类型的详细信息。 ### 7.1. 常用的方法 #### 7.1.1. Type[] getBounds() 获取泛型变量类型的上边界,如果未明确什么上边界默认为Object。例如:class Test<K extend Person>中K的上边界只有一个,是Person;而class Test<T extend List & Iterable>中T的上边界有2个,是List和Iterable #### 7.1.2. D getGenericDeclaration() 获取声明该泛型变量的原始类型,例如:class Test<K extend Person>中的K为泛型变量,这个泛型变量时Test类定义的时候声明的,说明如果调用getGenericDeclaration方法返回的就是Test对应的Class对象。 还有方法中也可以定义泛型类型的变量,如果在方法中定义,那么上面这个方法返回的就是定义泛型变量的方法了,返回的就是Method对象。 #### 7.1.3. String getName() 获取在源码中定义时的名字,如:class Test<K extend Person>就是K;class Test1<T>中就是T。 ## 8. WildcardType接口 表示的是通配符泛型,通配符使用问号表示,例如:? extends Number和? super Integer。 ### 8.1. 常用方法 接口中定义了2个方法。 #### 8.1.1. Type[] getUpperBounds() 返回泛型变量的上边界列表。 #### 8.1.2. Type[] getLowerBounds() 返回泛型变量的下边界列表。 ## 9. GenericArrayType接口 表示的是数组类型,且数组中的元素是ParameterizedType或者TypeVariable。 例如:List<String>[]或者T[]。 ### 9.1. 常用方法 这个接口只有一个方法: #### 9.1.1. Type getGenericComponentType() 这个方法返回数组的组成元素。 上面的大家多看几遍,下面开始上案例,加深对上面的理解和应用,信息量会比较大,慢慢理解。 ## 10. 泛型变量 泛型变量可以在类中和方法中定义。 泛型变量类型的使用TypeVariable接口来表示,所以可以通过TypeVariable接口获取泛型变量的所有信息。 下面我们分别来看看类中定义泛型变量和方法中定义泛型变量的用法以及泛型变量信息的获取。 ## 11. 类中定义泛型变量 ### 11.1. 语法 ```java class 类名<泛型变量1,泛型变量2,泛型变量3 extends 上边界1,泛型变量4 extends 上边界类型1 & 上边界类型2 & 上边界类型3> ``` - 泛型变量需要在类名后面的括号中定义 - 每个类中可以定义多个泛型变量,多个泛型变量之间用逗号隔开 - 泛型变量可以通过extends关键字指定上边界,上边界可以对泛型变量起到了限定的作用,上边界可以指定0到多个,多个之间需要用&符号隔开,如果不指定上边界,默认上边界为Object类型 ### 11.2. 案例代码 ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; interface Demo1I1 { //@1 } interface Demo1I2 { //@2 } /** * 类中泛型变量案例 * * @param <T1> * @param <T2> * @param <T3> */ public class Demo1<T1, T2 extends Integer, T3 extends Demo1I1 & Demo1I2> { //@3 public static void main(String[] args) { TypeVariable<Class<Demo1>>[] typeParameters = Demo1.class.getTypeParameters();//@4 for (TypeVariable<Class<Demo1>> typeParameter : typeParameters) { System.out.println("变量名称:" + typeParameter.getName()); System.out.println("这个变量在哪声明的:" + typeParameter.getGenericDeclaration()); Type[] bounds = typeParameter.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } System.out.println("--------------------"); } } } ``` 代码解读: @1:创建了接口Demo1I1,后面会用到 @2:创建接口Demo1I2,后面会用到这个接口 @3:创建了一个类Demo1,注意这个类是泛型类型的,泛型中定义了3个泛型类型变量,分别是:T1、T2、T3,这三个变量是有区别的。 T1没有限制上边界,默认上边界就是Object类型了。 注意T2的写法: ```java T2 extends Integer ``` 这个通过extends限定了T2的上边界为Integer。 再来看看T3的写法,比较特别: ```java T3 extends I1 & I2 ``` T3的上边界有多个,多个边界之间需要用&连接起来,T3有2个上边界,分别是两个接口Demo1I1和Demo1I2。 @4:这行代码用来调用了Class对象的getTypeParameters方法,这个方法会返回当前类上定义的泛型变量类型列表,当前类上定义了3个泛型变量类型,泛型变量类型在java中使用TypeVariable接口表示的。 后面的for循环就是输出泛型变量的信息了,我们来运行一下看看效果: ```java 变量名称:T1 这个变量在哪声明的:class com.javacode2018.chat05.demo11.Demo1 这个变量上边界数量:1 这个变量上边界清单: java.lang.Object -------------------- 变量名称:T2 这个变量在哪声明的:class com.javacode2018.chat05.demo11.Demo1 这个变量上边界数量:1 这个变量上边界清单: java.lang.Integer -------------------- 变量名称:T3 这个变量在哪声明的:class com.javacode2018.chat05.demo11.Demo1 这个变量上边界数量:2 这个变量上边界清单: com.javacode2018.chat05.demo11.Demo1I1 com.javacode2018.chat05.demo11.Demo1I2 -------------------- ``` 输出中可以看到3个泛型变量都是在当前类Demo1中定义的,每个泛型变量的名称,以及泛型变量的上边界信息都详细输出来了。 ## 12. 方法中定义泛型变量 ### 12.1. 语法 ```java 方法修饰符 <泛型变量1,泛型变量2,泛型变量3 extends 上边界1,泛型变量4 extends 上边界类型1 & 上边界类型2 & 上边界类型3> 方法名称(参数1类型 参数1名称,参数2类型 参数2名称) ``` - 泛型变量需要在方法名称前面的括号中定义 - 方法中可以定义多个泛型变量,多个泛型变量之间用逗号隔开 - 泛型变量可以通过extends关键字指定上边界,上边界可以对泛型变量起到了限定的作用,上边界可以指定0到多个,多个之间需要用&符号隔开,如果不指定上边界,默认上边界为Object类型 ### 12.2. 案例代码 ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; interface Demo2I1 { //@1 } interface Demo2I2 { //@2 } /** * 泛型方法中的泛型变量 */ public class Demo2 { public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s) {//@3 return t3; } public static void main(String[] args) { //获取Demo2中声明的所有方法 Method[] methods = Demo2.class.getDeclaredMethods(); Method m1 = null; //找到m1方法 for (Method method : methods) { if (method.getName().equals("m1")) { m1 = method; break; } } //获取方法的泛型参数列表 System.out.println("m1方法参数类型列表信息:----------"); Type[] genericParameterTypes = m1.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { //3个参数都是泛型变量类型的,对应java中的TypeVariable if (genericParameterType instanceof TypeVariable) { TypeVariable pt = (TypeVariable) genericParameterType; System.out.println("变量类型名称:" + pt.getTypeName()); System.out.println("变量名称:" + pt.getName()); System.out.println("这个变量在哪声明的:" + pt.getGenericDeclaration()); Type[] bounds = pt.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } } else if (genericParameterType instanceof Class) { Class pt = (Class) genericParameterType; System.out.println("参数类型名称:" + pt.getTypeName()); System.out.println("参数类名:" + pt.getName()); } System.out.println("--------------------"); } //获取方法的返回值,也是一个泛型变量 System.out.println("m1方法返回值类型信息:----------"); Type genericReturnType = m1.getGenericReturnType(); if (genericReturnType instanceof TypeVariable) { TypeVariable pt = (TypeVariable) genericReturnType; System.out.println("变量名称:" + pt.getName()); System.out.println("这个变量在哪声明的:" + pt.getGenericDeclaration()); Type[] bounds = pt.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } System.out.println("--------------------"); } //获取方法中声明的泛型参数列表 System.out.println("m1方法中声明的泛型变量类型列表:----------"); TypeVariable<Method>[] typeParameters = m1.getTypeParameters(); for (TypeVariable<Method> pt : typeParameters) { System.out.println("变量类型名称:" + pt.getTypeName()); System.out.println("变量名称:" + pt.getName()); System.out.println("这个变量在哪声明的:" + pt.getGenericDeclaration()); Type[] bounds = pt.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } System.out.println("--------------------"); } } } ``` @1 @2声明接口,下面会使用。 @3 这行比较特别,创建了一个方法,如下: ```java public <T1, T2 extends Integer, T3 extends Demo2I1 & Demo2I2> T3 m1(T1 t1, T2 t2, T3 t3, String s) ``` m1方法前面的<>括号中定义了3个泛型类型变量,方法有4个参数,前3个参数的类型为泛型变量类型的,第4个参数为String类型的。 泛型变量类型在java中使用TypeVariable表示,前3个参数都是泛型变量类型的,所以最后他们的信息都可以使用TypeVariable接口获取,最后一个参数是String类型的,这个是非泛型类型,使用Class类型来表示。 上面代码中先获取m1方法对应的Method对象,然后通过Method中的方法获取了方法参数的列表,方法的返回值详细的泛型信息,方法中声明的3个泛型变量的详细信息。 ## 13. 泛型类型 ### 13.1. 泛型类型定义的语法 ```java 具体类型<类型1,类型2,类型3> ``` - **泛型类型可以作为方法的参数、方法的返回值、泛型类(这3种一会举例)** - <>中的泛型的实际参数列表,可以有多个,可以是任意类型的,比如:String类型、自定义类型、泛型变量类型、泛型通配符类型(?表示通配符,这个一会后面会讲) - 泛型类型的信息在java中使用ParameterizedType接口来表示,可以通过这个接口作为入口获取泛型的具体详细信息。 **比如:List<String>、Map<Integer,String>、UserMapper<UserModel>,List<?>这些都是泛型类型,这些泛型的信息都可以通过ParameterizedType来表示,然后通过这个接口中的方法获取这些泛型的具体信息。** 下面来详解3种泛型类型。 ### 13.2. 方法中泛型参数和泛型返回值 方法的参数为泛型类型或者返回值为泛型类型,我们来获取这些泛型类型的信息。 #### 13.2.1. 案例代码 ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.List; import java.util.stream.Collectors; /** * 泛型参数 */ public class Demo4<T> {//@0 public class C1 {//@1 /** * m1方法参数和返回值都是泛型类型,泛型的实际类型是泛型变量类型T,T是在Demo4中声明的 * * @param list * @return */ public List<T> m1(List<T> list) {//@2 //对list做一些操作 return list; } } public static void main(String[] args) throws NoSuchMethodException { //获取m1方法 Method m1 = Demo4.C1.class.getMethod("m1", List.class); //调用Method中的getGenericParameterTypes方法可以获取参数类型列表,包含了详细的泛型信息 Type arg1Type = m1.getGenericParameterTypes()[0]; //m1方法有1个参数是泛型类型的,泛型类型java中用ParameterizedType接口表示 System.out.println("----------m1方法参数类型信息------------"); if (arg1Type instanceof ParameterizedType) {//@3 ParameterizedType parameterizedType = (ParameterizedType) arg1Type; System.out.println("原始类型:" + parameterizedType.getRawType()); System.out.println("所属的类型:" + parameterizedType.getOwnerType()); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); //泛型中第一个参数的类型是T,T是泛型变量,泛型变量对应java中的TypeVariable接口 Type oneType = actualTypeArguments[0];//@4 System.out.println("@5:" + oneType.getClass());//@5 if (oneType instanceof TypeVariable) { System.out.println("这个参数是个泛型变量类型!"); TypeVariable<Class<Demo4>> oneActualType = (TypeVariable) oneType; System.out.println("变量名称:" + oneActualType.getName()); System.out.println("这个变量在哪声明的:" + oneActualType.getGenericDeclaration()); Type[] bounds = oneActualType.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } } } System.out.println("----------m1方法返回值类型信息------------"); //m1方法返回值是泛型类型的,泛型类型java中用ParameterizedType接口表示 //Method类中的getGenericReturnType方法可以获取方法的返回值,如果返回值是泛型类型的,会获取泛型类型对应的具体类型,此处返回的是List<String>类型的,java中使用ParameterizedType接口表示 Type returnType = m1.getGenericReturnType(); if (returnType instanceof ParameterizedType) {//@6 ParameterizedType parameterizedType = (ParameterizedType) returnType; System.out.println("原始类型:" + parameterizedType.getRawType()); System.out.println("所属的类型:" + parameterizedType.getOwnerType()); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); //泛型中第一个参数的类型是T,T是泛型变量,泛型变量对应java中的TypeVariable接口 Type oneType = actualTypeArguments[0];//@7 System.out.println("@8:" + oneType.getClass());//@8 if (oneType instanceof TypeVariable) { System.out.println("返回值是个泛型变量类型!"); TypeVariable<Class<Demo4>> oneActualType = (TypeVariable) oneType; System.out.println("变量名称:" + oneActualType.getName()); System.out.println("这个变量在哪声明的:" + oneActualType.getGenericDeclaration()); Type[] bounds = oneActualType.getBounds(); System.out.println("这个变量上边界数量:" + bounds.length); System.out.println("这个变量上边界清单:"); for (Type bound : bounds) { System.out.println(bound.getTypeName()); } System.out.println("--------------------"); } } } } ``` 代码解读: @0:Demo1<T>声明了一个泛型类型的变量T;T是个泛型类型的变量,泛型类型的变量在java中使用TypeVariable来表示。 @1:创建了一个类C1,注意这个类是在Demo1的内部声明的,说明C1是一个内部类。 @2:创建了方法m1,m1的参数和返回值都是泛型类型的List<T>,泛型类型在java中用ParameterizedType接口表示;而List<T>泛型类型中还有一个类型T,T是泛型变量类型的,在java中使用TypeVariable接口表示。 上面代码中输出了m1方法参数的泛型的详细信息。 我们来运行看一下结果: ```java ----------m1方法参数类型信息------------ 原始类型:interface java.util.List 所属的类型:null @5:class sun.reflect.generics.reflectiveObjects.TypeVariableImpl 这个参数是个泛型变量类型! 变量名称:T 这个变量在哪声明的:class com.javacode2018.chat05.demo11.Demo4 这个变量上边界数量:1 这个变量上边界清单: java.lang.Object ----------m1方法返回值类型信息------------ 原始类型:interface java.util.List 所属的类型:null @8:class sun.reflect.generics.reflectiveObjects.TypeVariableImpl 返回值是个泛型变量类型! 变量名称:T 这个变量在哪声明的:class com.javacode2018.chat05.demo11.Demo4 这个变量上边界数量:1 这个变量上边界清单: java.lang.Object -------------------- ``` ### 13.3. 泛型类 #### 13.3.1. 泛型类的定义 ```java 类修饰符 类名<类型1,类型2,类型n>{ } ``` 上面是定义了一个泛型类,<>中包含的是一些可以变类型的列表,实际上我们创建这个类的对象的时候,会明确指定<>中包含的具体类型。 比如我们熟悉的HashMap就是一个泛型类,来看看这个类的定义: ```java public class HashMap<K,V> ``` K和V是泛型变量类型的,具体是什么类型,可以在创建HashMap的时候去随意指定。 现在我们想获取泛型对象<>中包含的具体的类型,怎么获取? 比如下面代码: ```java package com.javacode2018.chat05.demo11; public class Demo5<T1, T2> { //@1 public void m1(Demo5<T1, T2> demo) { //@2 System.out.println(demo.getClass()); } public static void main(String[] args) { Demo5<String, Integer> demo5 = new Demo5<>();//@3 demo5.m1(demo5); } } ``` @1:Demo5类中定义了两个泛型变量类型T1和T2。 @2:m1方法参数类型为Demo5,在这个方法内部如果我们想获取这个参数具体的详细类型信息,上面的代码是获取不到的,只能获取到demo5参数所属的类型是Demo5,但是无法获取到Demo5中的T1和T2这两个泛型变量类型对应的具体类型。 运行一下上面代码输出: ```java class com.javacode2018.chat05.demo11.Demo5 ``` Class对象中有个方法比较牛逼: ```java public Type getGenericSuperclass() ``` 这个方法相当牛逼,可以获取到父类中泛型详细信息。 来看一个案例就明白了: ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; //泛型类 class Demo<T1, T2> {//@0 } public class Demo6 extends Demo<String, Integer> { //@1 public static void main(String[] args) { Demo6 demo6 = new Demo6(); //demo6Class对应的是Demo6的Class对象 Class<? extends Demo6> demo6Class = demo6.getClass();//@2 //获取Demo6的父类的详细类型信息,包含泛型信息 Type genericSuperclass = demo6Class.getGenericSuperclass(); //@3 // 泛型类型用ParameterizedType接口表示,输出看一下是不是这个接口类型的 System.out.println(genericSuperclass.getClass()); //@4 if (genericSuperclass instanceof ParameterizedType) { //@5 ParameterizedType pt = (ParameterizedType) genericSuperclass; System.out.println(pt.getRawType()); Type[] actualTypeArguments = pt.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument.getTypeName()); } System.out.println(pt.getOwnerType()); } } } ``` 运行输出: ```java com.javacode2018.chat05.demo11.Demo6 class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl class com.javacode2018.chat05.demo11.Demo java.lang.String java.lang.Integer null ``` 代码解读: @0:声明了一个泛型类,泛型类中定义了两个泛型变量的类型T1和T2,这两个变量的具体类型,可以在创建对象的时候指定任意具体的类型。 @1:这个比较特殊了,创建了类Demo6,这个类继承了Demo类,并且注意Demo后面的部分<String, Integer>,这个指定了具体的类型了,此时T1的具体类型就是String类型了,T2对应的具体类型就是Integer类型了。 @2:获取Demo6对应的Class对象 @3:这行代码比较关键,这个调用了Class类中的getGenericSuperclass方法,这个方法可以获取当前类父类的具体类型信息,如果父类是泛型,则会返回泛型详细信息,泛型类型在java中用ParameterizedType接口表示,所以@3代码返回的类型一定是ParameterizedType接口类型的了。 @4:输出了genericSuperclass变量的类型,注意上面第2行输出:ParameterizedTypeImpl,这个是ParameterizedType接口的一个实现类。 @5:这个地方加了个判断,判断是不是ParameterizedType类型的,然后if内部输出了泛型类型的具体的信息,调用了ParameterizedType接口中的3个方法去获取了具体的参数类型的信息,输出中的5/6行可以看到输出了具体的类型String和Integer。 根据上面代码的原理,我们可以将下面的代码进行改造: ```java package com.javacode2018.chat05.demo11; public class Demo5<T1, T2> { //@1 public void m1(Demo5<T1, T2> demo5) { //@2 System.out.println(demo5.getClass()); } public static void main(String[] args) { Demo5<String, Integer> demo5 = new Demo5<>();//@3 demo5.m1(demo5); } } ``` 如果我们想获取Demo5的具体信息,需要给Demo5创建一个之类才可以,此处我们可以使用java中的匿名内部类来友好的解决这个问题,将上面代码变换一下,变成下面这样: ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; public class Demo5<T1, T2> { //@1 public void m1(Demo5<T1, T2> demo) { //@2 //demo6Class对应的是Demo6的Class对象 Class<? extends Demo5> demoClass = demo.getClass(); //获取Demo6的父类的详细类型信息,包含泛型信息 Type genericSuperclass = demoClass.getGenericSuperclass(); // 泛型类型用ParameterizedType接口表示,输出看一下是不是这个接口类型的 System.out.println(genericSuperclass.getClass()); if (genericSuperclass instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) genericSuperclass; System.out.println(pt.getRawType()); Type[] actualTypeArguments = pt.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument.getTypeName()); } System.out.println(pt.getOwnerType()); } } public static void main(String[] args) { Demo5<String, Integer> demo5 = new Demo5<String, Integer>() { };//@3 demo5.m1(demo5); } } ``` 关键代码在@3,这个地方利用了一个匿名内部类,相当于创建了Demo5的一个子类,并且指定了Demo5中两个泛型变量类型的具体类型。 运行代码输出: ```java class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl class com.javacode2018.chat05.demo11.Demo5 java.lang.String java.lang.Integer null ``` 这次我们获取到了泛型类中具体的类型了。 这种玩法在fastjson中有用到,再来看个案例: ```java package com.javacode2018.chat05.demo11; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import lombok.*; import java.io.Serializable; public class Demo7 { /** * 通用的返回值类型 * * @param <T> */ @Getter @Setter @ToString @Builder @NoArgsConstructor @AllArgsConstructor public static class Result<T> implements Serializable { //@1 private String code; private String subCode; private String msg; private T data; } @Getter @Setter @Builder @ToString @NoArgsConstructor @AllArgsConstructor public static class UserModel { //@2 private Integer id; private String name; } /** * 返回一个用户信息 * * @return */ public static Result<UserModel> getUser() { //@3 UserModel userModel = UserModel.builder().id(1).name("路人甲Java").build(); Result<UserModel> result = Result.<UserModel>builder().code("1").subCode(null).msg("操作成功").data(userModel).build(); return result; } /** * 用户json信息 * * @return */ public static String getUserString() { //@4 return JSON.toJSONString(getUser()); } public static void main(String[] args) { String userString = getUserString(); //会输出:{"code":"1","data":{"id":1,"name":"路人甲Java"},"msg":"操作成功"} System.out.println(userString); //@5 //下面我们需要将userString反序列化为Result<UserModel>对象 Result<UserModel> userModelResult = JSON.parseObject(userString, new TypeReference<Result<UserModel>>() { }); //@6 //我们来看看Result中的data是不是UserModel类型的 System.out.println(userModelResult.getData().getClass()); //@6 } } ``` 先看看运行结果: ```java {"code":"1","data":{"id":1,"name":"路人甲Java"},"msg":"操作成功"} class com.javacode2018.chat05.demo11.Demo7$UserModel ``` @1:创建了一个Result类型的,这个类型可以作为任何接口通用的返回值类型,这个大家可以借鉴,接口有几个通用的字段:code:状态码,subCode:子状态码,data:具体的任何类型的数据,data的具体类型可以在创建Result的时候指定,msg:接口返回的提示信息(如错误提示,操作成功等信息)。 @2:创建了一个用户类 @3:这个方法模拟返回一个用户的信息,用户信息封装在Result中。 @4:将用户信息转换为json字符串返回 @5:输出了用户信息字符串,也就是上面输出中的第一行的内容。 @6:这个是上面代码的关键,调用了fastjson中的方法,将字符串反序列化为Result<UserModel>,fastjson是如何获取泛型类Result中T的具体的类型的呢,T具体的类型对应的是UserModel,关键代码就是下面这行代码: ```java new TypeReference<Result<UserModel>>() { } ``` 这个相当于创建了一个TypeReference类的一个子类,注意TypeReference后面尖括号中的东西,是<UserModel>,通过这个指定了泛型变量类型的具体类型,我们去看一下TypeReference类源码,上一些关键代码: ```java public class TypeReference<T> { protected TypeReference(){ Type superClass = getClass().getGenericSuperclass(); //@1 Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; //@2 Type cachedType = classTypeCache.get(type); if (cachedType == null) { classTypeCache.putIfAbsent(type, type); cachedType = classTypeCache.get(type); } this.type = cachedType; } } ``` 注意上面的@1和@2是不是很熟悉了,fastjson中获取泛型的具体类型也是让我们采用匿名内部类去实现的,最后内部也是调用getGenericSuperclass去获取具体的泛型类型中具体的类型的。 fastjson maven配置: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> ``` ### 13.4. 通配符类型 通配符在java中 使用?表示,例如:? extends Number和? super Integer。 java中通配符对应的类型是WildcardType接口,可以通过这个接口来获取通配符具体的各种信息。 #### 13.4.1. 通配符上边界 通配符具体的类型,可以任意指定,但是我们可以限定通配符的上边界,上边界指定了这个通配符能够表示的最大的范围的类型。 比如:?extends Integer,那么?对应的具体类型只能是Integer本身或者其子类型。 #### 13.4.2. 通配符上边界 也可以给通配符指定下边界,下边界定义了通配符能够表示的最小的类型。 比如:? super C1,那么?对应的具体类型只能是C1类型或者C1的父类型。 ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.List; import java.util.Map; public class Demo8 { public static class C1 { //@1 } public static class C2 extends C1 { //@2 } public static List<?> m1(Map<? super C2, ? extends C1> map) { //@3 return null; } public static void main(String[] args) throws NoSuchMethodException { Method m1 = Demo8.class.getMethod("m1", Map.class); //获取m1方法参数泛型详细参数信息 System.out.println("获取m1方法参数泛型详细参数信息"); Type[] genericParameterTypes = m1.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { // m1的参数为Map<? super C2, ? extends C1>,这个是泛型类型的,所以是ParameterizedType接口类型 if (genericParameterType instanceof ParameterizedType) { //@4 ParameterizedType parameterizedType = (ParameterizedType) genericParameterType; //@5 //下面获取Map后面两个尖括号中的泛型参数列表,对应? super C2, ? extends C1这部分的内容,这部分在java中对应WildcardType接口类型 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); //@6 for (Type actualTypeArgument : actualTypeArguments) { if (actualTypeArgument instanceof WildcardType) { WildcardType wildcardType = (WildcardType) actualTypeArgument; //获取通配符的名称,输出是? System.out.println("通配符类型名称:" + wildcardType.getTypeName());//@7 //获取通配符的上边界 Type[] upperBounds = wildcardType.getUpperBounds(); for (Type upperBound : upperBounds) { System.out.println("通配符上边界类型:" + upperBound.getTypeName()); } //获取通配符的下边界 Type[] lowerBounds = wildcardType.getLowerBounds(); for (Type lowerBound : lowerBounds) { System.out.println("通配符下边界类型:" + lowerBound.getTypeName()); } System.out.println("------------"); } } } } //获取返回值通配符详细信息 System.out.println("获取m1方法返回值泛型类型详细信息"); Type genericReturnType = m1.getGenericReturnType(); // m1的返回值是List<?>,这个是个泛型类型,对应ParameterizedType接口,泛型中的具体类型是个通配符类型,通配符对应WildcardType接口类型 if (genericReturnType instanceof ParameterizedType) { //@4 ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; //@5 //下面获取List面两个尖括号中的泛型参数列表,对应?这部分的内容,这个是个通配符类型,这部分在java中对应WildcardType接口 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { if (actualTypeArgument instanceof WildcardType) { WildcardType wildcardType = (WildcardType) actualTypeArgument; //获取通配符的名称,输出是? System.out.println("通配符类型名称:" + wildcardType.getTypeName()); //获取通配符的上边界 Type[] upperBounds = wildcardType.getUpperBounds(); for (Type upperBound : upperBounds) { System.out.println("通配符上边界类型:" + upperBound.getTypeName()); } //获取通配符的下边界 Type[] lowerBounds = wildcardType.getLowerBounds(); for (Type lowerBound : lowerBounds) { System.out.println("通配符下边界类型:" + lowerBound.getTypeName()); } System.out.println("------------"); } } } } } ``` 输出: ```java 获取m1方法参数泛型详细参数信息 通配符类型名称:? super com.javacode2018.chat05.demo11.Demo8$C2 通配符上边界类型:java.lang.Object 通配符下边界类型:com.javacode2018.chat05.demo11.Demo8$C2 ------------ 通配符类型名称:? extends com.javacode2018.chat05.demo11.Demo8$C1 通配符上边界类型:com.javacode2018.chat05.demo11.Demo8$C1 ------------ 获取m1方法返回值泛型类型详细信息 通配符类型名称:? 通配符上边界类型:java.lang.Object ------------ ``` 具体的就不解释了,只需要注意一点?通配符的信息使用WildcardType接口表示,可以通过这个接口获取通配符的详细信息。 ## 14. 泛型数组 ### 14.1. 什么是泛型数组? 数组中的元素为泛型,那么这个数组就是泛型类型的数组,泛型数组在java中使用GenericArrayType接口来表示,可以通过这个接口提供的方法获取泛型数组更详细的信息。 如:List<String> list []; List<String> list []\[]; 泛型数组类型的可以作为方法的参数、方法的返回值、泛型类的具体类型、字段的类型等等。 下面就以泛型字段来举例,一起来获取泛型字段的详细信息。 ```java package com.javacode2018.chat05.demo11; import java.lang.reflect.*; import java.util.List; import java.util.Map; public class Demo9 { List<String> list[]; //@1 public static void main(String[] args) throws NoSuchFieldException { Field list = Demo9.class.getDeclaredField("list"); //获取字段的泛型类型 Type genericType = list.getGenericType(); //@2 //看看字段的具体泛型类型 System.out.println(genericType.getClass()); //@3 if (genericType instanceof GenericArrayType) { GenericArrayType genericArrayType = (GenericArrayType) genericType; //获取数组的具体类型,具体的类型就是List<String>,这个是个泛型类型,对应java中的ParameterizedType接口 Type genericComponentType = genericArrayType.getGenericComponentType();//@4 System.out.println(genericComponentType.getClass()); if (genericComponentType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericComponentType; System.out.println(parameterizedType.getRawType()); //调用getActualTypeArguments()获取List<String>中尖括号中的参数列表 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//@5 for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument.getTypeName()); } System.out.println(parameterizedType.getOwnerType()); } } } } ``` 运行输出: ```java class sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl interface java.util.List java.lang.String null ``` 代码解读: @1:声明了一个泛型类型的数组。 @2:获取list字段对应的泛型数组类型,泛型数组在java中使用GenericArrayType表示,所以@3输出是GenericArrayType接口类型的。 @4:调用GenericArrayType接口中的getGenericComponentType方法会返回数组的具体的泛型类型,这个地方对应的就是List<String>,这个是个泛型类型,泛型类型在java中使用ParameterizedType接口表示的。 @5:调用了ParameterizedType接口中的getActualTypeArguments方法,这个方法可以获取泛型类型中具体的类型列表,就是List后面尖括号中的参数列表。 ### 14.2. 综合案例 代码如下: ```java package com.javacode2018.chat05.demo11; import java.util.List; import java.util.Map; public class Demo10<K, V> { Map<String, ? extends List<? extends Map<K, V>>> [][] map; } ``` 上面这个挺复杂的,我们一步步拆解解析一下,步骤如下: ```java 1、Demo10<K, V>: --------> 对应java中的Class对象 2、<K, V>:定义了2个泛型变量,泛型变量对应TypeVariable接口 3、Map<String, ? extends List<? extends Map<K, V>>> [][]map:定义了一个二维泛型数组,泛型数组用GenericArrayType接口表示 4、map中的每个元素是这个是Map<String, ? extends List<? extends Map<K, V>>> []类型的,是一个一维泛型数组,泛型数组用GenericArrayType接口表示。 5、再继续拆解,Map<String, ? extends List<? extends Map<K, V>>> []中每个元素是Map<String, ? extends List<? extends Map<K, V>>>泛型类型的,泛型类型在java中使用ParameterizedType接口表示 6、拆解Map<String, ? extends List<? extends Map<K, V>>>后面尖括号中的参数列表,可以调用ParameterizedType接口的Type[] getActualTypeArguments()方法获取,Map后面的尖括号中有2个参数,分别是String和? extends List<? extends Map<K, V>> 7、String是java中定义的类型,对应java中的Class对象 8、? extends List<? extends Map<K, V>>是通配符类型的,对应WildcardType接口,通配符指定了上边界,上边界是List<? extends Map<K, V>> 9、List<? extends Map<K, V>>又是一个泛型类型,泛型类型对应ParameterizedType接口,List<? extends Map<K, V>>的尖括号中又定义了这个泛型类型的具体的类型? extends Map<K, V> 10、? extends Map<K, V>又是一个通配符类型,对应WildcardType接口,这个通配符指定了上边界为Map<K,V> 11、Map<K,V>又对应泛型类型,泛型类型对应ParameterizedType接口,调用这个接口的getActualTypeArguments()方法获取泛型中的参数列表K和V 12、K和V是Demo10中定义的泛型变量类型,泛型变量类型对应TypeVariable接口 ``` 按照上面的思路,我们来完善一下解析代码: ```java package com.javacode2018.chat05.demo11; import sun.security.util.Length; import java.lang.reflect.*; import java.util.List; import java.util.Map; public class Demo10<K, V> { Map<String, ? extends List<? extends Map<K, V>>>[][] map; public static void parseType(Type type, int level) { String whileString = whileString(level); if (type instanceof GenericArrayType) { System.out.println(whileString + "泛型数组类型:" + type); parseType(((GenericArrayType) type).getGenericComponentType(), ++level); } else if (type instanceof ParameterizedType) { System.out.println(whileString + "泛型类型:" + type); ParameterizedType parameterizedType = (ParameterizedType) type; System.out.println(whileString + "实际类型:" + parameterizedType.getRawType()); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); System.out.println(whileString + actualTypeArguments.length + "个泛型参数,如下:"); int count = 0; for (Type actualTypeArgument : actualTypeArguments) { if (count++ == 0) { level++; } parseType(actualTypeArgument, level); } } else if (type instanceof WildcardType) { System.out.println(whileString + "通配符类型:" + type); WildcardType wildcardType = ((WildcardType) type); System.out.println(whileString + "通配符类型名称:" + wildcardType.getTypeName()); Type[] upperBounds = wildcardType.getUpperBounds(); System.out.println(whileString + "上边界列表"); int count = 0; for (Type upperBound : upperBounds) { if (count++ == 0) { level++; } parseType(upperBound, level); } System.out.println(whileString + "下边界列表"); Type[] lowerBounds = wildcardType.getLowerBounds(); for (Type lowerBound : lowerBounds) { if (count++ == 0) { level++; } parseType(lowerBound, level); } } else if (type instanceof TypeVariable) { System.out.println(whileString + "泛型变量类型:" + type); TypeVariable typeVariable = ((TypeVariable) type); Type[] bounds = typeVariable.getBounds(); System.out.println(whileString + "泛型变量上边界列表"); int count = 0; for (Type bound : bounds) { if (count++ == 0) { level++; } parseType(bound, level); } } else if (type instanceof Class) { System.out.println(whileString + "普通类型:" + ((Class) type).getName()); } } public static String whileString(int count) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.append("----"); } return sb.toString(); } public static void main(String[] args) throws NoSuchFieldException { parseType(Demo10.class.getDeclaredField("map").getGenericType(), 0); } } ``` 运行输出: ```java 泛型数组类型:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>>[][] ----泛型数组类型:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>>[] --------泛型类型:java.util.Map<java.lang.String, ? extends java.util.List<? extends java.util.Map<K, V>>> --------实际类型:interface java.util.Map --------2个泛型参数,如下: ------------普通类型:java.lang.String ------------通配符类型:? extends java.util.List<? extends java.util.Map<K, V>> ------------通配符类型名称:? extends java.util.List<? extends java.util.Map<K, V>> ------------上边界列表 ----------------泛型类型:java.util.List<? extends java.util.Map<K, V>> ----------------实际类型:interface java.util.List ----------------1个泛型参数,如下: --------------------通配符类型:? extends java.util.Map<K, V> --------------------通配符类型名称:? extends java.util.Map<K, V> --------------------上边界列表 ------------------------泛型类型:java.util.Map<K, V> ------------------------实际类型:interface java.util.Map ------------------------2个泛型参数,如下: ----------------------------泛型变量类型:K ----------------------------泛型变量上边界列表 --------------------------------普通类型:java.lang.Object ----------------------------泛型变量类型:V ----------------------------泛型变量上边界列表 --------------------------------普通类型:java.lang.Object --------------------下边界列表 ------------下边界列表 ``` 上将map这个属性详细的泛型信息都输出出来了,重点在于上面的parseType方法,java中的类型无非就是5种表示类型,这个方法内部使用递归来解析这些类型。 ## 15. 总结 1. 泛型解析需要一步步拆解,会被拆解为5中类型中的一种,需要理解5中类型分别对应什么,这个是关键 2. 本篇内容比较多,建议每个案例大家都去自己写一下,体验一下,加深理解 3. 如果对泛型有任何疑问的可以留言交流 <a style="display:none" target="_blank" href="https://mp.weixin.qq.com/s/_S1DD2JADnXvpexxaBwLLg" style="color:red; font-size:20px; font-weight:bold">继续收门徒,亲手带,月薪 4W 以下的可以来找我</a> ## 最新资料 1. <a href="https://mp.weixin.qq.com/s?__biz=MzkzOTI3Nzc0Mg==&mid=2247484964&idx=2&sn=c81bce2f26015ee0f9632ddc6c67df03&scene=21#wechat_redirect" target="_blank">尚硅谷 Java 学科全套教程(总 207.77GB)</a> 2. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484192&idx=1&sn=505f2faaa4cc911f553850667749bcbb&scene=21#wechat_redirect" target="_blank">2021 最新版 Java 微服务学习线路图 + 视频</a> 3. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484573&idx=1&sn=7f3d83892186c16c57bc0b99f03f1ffd&scene=21#wechat_redirect" target="_blank">阿里技术大佬整理的《Spring 学习笔记.pdf》</a> 4. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247484544&idx=2&sn=c1dfe907cfaa5b9ae8e66fc247ccbe84&scene=21#wechat_redirect" target="_blank">阿里大佬的《MySQL 学习笔记高清.pdf》</a> 5. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485167&idx=1&sn=48d75c8e93e748235a3547f34921dfb7&scene=21#wechat_redirect" target="_blank">2021 版 java 高并发常见面试题汇总.pdf</a> 6. <a href="https://mp.weixin.qq.com/s?__biz=MzkwOTAyMTY2NA==&mid=2247485664&idx=1&sn=435f9f515a8f881642820d7790ad20ce&scene=21#wechat_redirect" target="_blank">Idea 快捷键大全.pdf</a> ![](https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)
#custom-toc-container