封面来源:本文封面来源于网络,如有侵权,请联系删除。
本文 JDK 版本为 JDK 17
1. 注解
1.1 自定义注解
使用 @interface
自定义注解,就会自动继承 java.lang.annotation.Annotation
接口。
@interface
可以用来声明一个注解,格式:public @interface 注解名 {定义内容}
。注解中每一个方法表示声明了一个配置参数,而方法的名称就是参数的名称,返回值类型就是参数的类型(返回值只能是基本类型、Class、String 和 enum),并且可以通过 default
关键词来声明参数的默认值。如果只有一个参数成员,一般参数名为 value。
注解元素必须要有值。定义注解元素时,经常使用空字符串或 0 作为默认值。
1.2 元注解
元注解的作用就是负责注解其他注解,Java 定义了 4 个标准的元注解(meta-annotation)类型,它们被用来提供对其它注解(annotation)类型作说明。
这四个元注解位于 java.lang.annotation
包中,它们分别是:
1 2 3 4 @Target @Retention @Documented @Inherited
2. 类的加载
2.1 类的加载
类的加载
将 Class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 java.lang.Class
对象。
类的连接
类的连接是指将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。主要分为三步,分别是验证、准备和解析。
1、验证:确保加载的类信息符合 JVM 规范,即加载的 Class 文件的格式是否正确。
2、准备:正式为类的静态变量(static)分配内存并为其设置默认初始值的阶段。
3、解析:虚拟机常量池内的符号引用(变量名)替换为直接引用(地址)的过程。
类的初始化
类的初始化就是执行类构造器 <clinit>()
方法的过程。类构造器 <clinit>()
方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的 <clinit>()
方法在多线程环境中被正确加锁和同步。
2.2 类初始化
类的主动引用一定会发生类的初始化。 比如:
1、当虚拟机启动时,初始化 main()
方法所在的类;
2、new
一个对象;
3、调用类的静态成员(final 常量除外)和静态方法;
4、使用 java.lang.reflect
包中的方法对类进行反射调用;
5、初始化一个类时,如果其父类没有被初始化,则会先初始化它的父类。
类的被动引用不会发生类的初始化。 比如:
1、当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化;
2、通过数组定义类引用,不会触发此类的初始化;
3、引用常量不会触发其所在类的初始化(常量在连接阶段就存入调用类的常量池中了)。
2.3 类加载器
类加载器
类加载器(ClassLoader)的作用:将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class
对象,作为方法区中类数据的访问入口。
简单来说,类的加载阶段有这样一个动作:通过一个类的全限定名来获取描述此类的二进制字节流。这个动作放到了 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类,而实现这个动作的代码模块就叫做类加载器。
类加载器在 Java 程序中起到的作用不局限于类加载阶段。为了确定任意一个类在 JVM 中的唯一性,除了这个类本身以外,还需要加上加载这个类的类加载器作为依据。每一个类加载器,都拥有一个独立的类命名空间。
也就是说,要判断两个类是否相等,只有在这两个类都是由同一个类加载器加载的前提下才有意义。因此就算是两个来自于同一 class 文件,被同一个虚拟机加载的类,但加载它们的类加载器不同,那也不能认为这两个类相等。这里的“相等”不仅仅指使用 equals()
方法比较,还包括 isAssignableFrom()
方法和 isInstance()
方法的返回结果,以及使用 instanceof
关键字进行判断。
拓展:Class#isAssignableFrom()
方法与 instanceof
关键词的使用。
Class#isAssignableFrom()
方法用于判断某个类是否是另一个类的父类,instanceof
关键词用于判断某个实例是否是某个父类类型。
使用方式:
1 2 3 父类.class.isAssignableFrom(子类.class) 子类实例 instanceof 父类类型
类加载器的种类
1、启动类加载器(Bootstrap ClassLoader),或者说引导类加载器、根加载器。这个类加载器由 C++ 编写,是 JVM 自带的类加载器(是 JVM 的一部分)。
2、其他的类加载器。这些类加载器由 Java 语言实现,不在 JVM 中,并且都继承自 java.lang.ClassLoader
。
几个系统级别的类加载器
1、启动类加载器(类加载器的种类中的第一种):这个类加载器负责将存放在 ${JAVA_HONE}\lib
目录中 ,或者被 XbootstrapPath
参数所指定的 目录中 ,并且是虚拟机 基于一定规则 (如文件名称规则,如 rt.jar)标识的 类库 加载到虚拟机内存中。该加载器无法通过 Java 程序直接获取,如果想委派到启动类加载器只需直接使用 null
替代即可(后面会说)。
2、扩展类加载器(Extension ClassLoader):该类加载器由 sun.misc.Launcher
的静态嵌套类 ExtClassLoader
实现。它负责将 ${JAVA_HONE}\lib\ext
目录下或通过 -D java.ext.dirs
系统变量指定的目录下的所有类库装入工作库。开发者可以直接使用此加载器。
3、应用程序类加载器(Application ClassLoader):该类加载器由 sun.misc.Launcher
的静态嵌套类 AppClassLoader
实现,由于该类加载器的实例是 ClassLoader
中静态方法 getSystemClassLoader()
中的返回值,因此这个类加载器也被成为 系统类加载器 。它负责加载用户类路径(ClassPath)或 -D java.class.path
所指的目录下的所有类库装入工作库,是最常用的加载器。开发者也可以直接使用此加载器。如果程序中没有自定义类加载器,一般情况下该类加载器就是程序中默认使用的类加载器。
4、线程上下文类加载器(Thread Context ClassLoader):后面再说。 😉
2.4 类加载器的工作流程
我们编写的 Java 程序都是由上面四种类加载器相互配合进行类加载的,当然还可以自定义类加载器。其中,启动类加载器、拓展类加载器、系统类加载器和自定义类加载器的关系如下:
像上图这样的层次关系被称为 双亲委派模型 ,或者说双亲委派机制。除了顶层的启动类加载器,其他的类加载器都应当有自己的父类加载器。类加载器与其父类加载器之间的关系不会以继承(Inheritance)来实现,而是以组合(Composition):
1 2 3 4 5 6 7 public abstract class ClassLoader { private final ClassLoader parent; }
简单验证下类加载器的层次关系:
1 2 3 4 5 6 7 @Test public void test () { ClassLoader classLoader = Person.class.getClassLoader(); System.out.println(classLoader); System.out.println(classLoader.getParent()); System.out.println(classLoader.getParent().getParent()); }
运行结果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@5caf905d
null
结果符合我们的预期,而最后打印出的 null
说明了 classLoader.getParent()
指的是启动类加载器,因为它没有父类加载器。
那双亲委派模型的工作机制是怎样的呢?
当一个类加载器收到了类加载请求时,它不会自己尝试去加载这个类,而是把这个请求委派给它的父类加载器,直到请求传递到顶层的启动类加载器。如果父类加载器无法完成当前的类加载请求(在它的搜索范围内没有找到需要加载的类),那么父类加载器又会把类加载请求委派给它的子类加载器。当然也可能直到最后,这个类加载请求也没法完成,这时就会抛出 ClassNotFoundException
异常。
这里还需要引出 类缓存 的概念。所谓类缓存,就是“标准的 JavaSE 类加载器可以按要求查找类,一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象”。
简单来说,类加载器会缓存自己已经加载过的类。当加载一个类时,首先会从缓存中加载,如果缓存中不存在,再按照前面所说的工作机制去加载类。
双亲委派模型的好处
使用双亲委派模型来协调类加载器之间的关系,可以使 Java 类随着加载它的类加载器一起具备一种带有优先级的层次关系。例如存放在 rt.java
中的 java.lang
包中的类,无论是哪个类加载器加载它们,最终都将由顶层的启动类加载器进行加载。
也正因如此,java.lang
包下的类在程序的各个类加载器中被加载时都是相等的(来自同一个 class 文件且被同一个类加载器加载)。
如果不这样,例如 java.lang
包下的 java.lang,Object
类被不同的类加载器加载时将不会相等,那么程序中就会出现多个 java.lang.Object
类。由于其他类都会隐式继承 java.lang.Object
类,当存在多个 Object
类时,程序将变得混乱。
双亲委派模型的实现
类加载器双亲委派模型的具体实现体现在 java.lang.ClassLoader
中,而其中的 loadClass()
方法则最能体现这个机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false ); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c == null ) { long t0 = System.nanoTime(); try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null ) { long t1 = System.nanoTime(); c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
2.5 破坏双亲委派模型
破坏双亲委派模型,或者说打破双亲委派机制,是什么意思?
在加载类时,如果不按照“自底向上检查是否已加载类,自顶向下尝试加载类”的方式去加载类,那么就可以叫做破坏双亲委派模型。
已经知道加载类的核心方法是抽象类 ClassLoader
中的 loadClass()
方法,可以通过继承这个抽象类并重写 loadClass()
方法,而且不按照双亲委派模型的方式去加载类,那么就可以打破双亲委派模型。
在 Java 发展史上也有双亲委派模型被破坏的情况,比如:
1、由于 java.lang.ClassLoader
在 JDK1.0 中已经存在,所以会有人继承这个抽象类并重写 loadClass()
方法来实现自定义类加载器。为了在 JDK1.2 中引入双亲委派模型并向前兼容,loadClass()
方法必须要保留并且能够被重写,于是在 ClassLoader
类添加了一个新的被 protected 修饰的 findClass()
方法,并告知开发者不要重写 loadClass()
而是重写 findClass()
。由于双亲委派模型的具体实现就在 loadClass()
方法内,并未禁止重写这个方法,因此委派的逻辑就被破坏了。
2、双亲委派模型存在缺陷:双亲委派模型很好地解决了各个类加载器加载基础类的统一问题(越基础的类由越上层的类加载器加载),这些基础类大多数情况下作为用户调用的基础类库,但 这些基础类无法回调用户的代码 。以 JDBC 为例,它规定了如何使用 Java 代码来连接数据库,具体的做法需要交由各个数据库厂商实现。JDBC 位于 rt.jar 中,由启动类加载器去加载,但其具体实现是在用户定义的 ClassPath 中,只能由应用类加载器去加载,因此启动类加载器只能委托子类加载器去加载数据库厂商们的具体实现,而这就破坏了双亲委派模型。具体实现方式是引入了线程上下文类加载器(Thread Context ClassLoader),可以通过 java.lang.Thread
的 setContextClassLoader()
方法来设置,然后利用 Thread.current.currentThread().getContextClassLoader()
获得类加载器来加载(如果直接获取,将获取到应用程序类加载器)。
3、用户对应用程序动态性的热切追求:如代码热替换(HotSwap)、热模块部署等,因此催生出 JSR-291
以及它的业界实现 OSGi,而 OSGi 定制了自己的类加载规则,利用自定义类加载器机制来完成模块化热部署,不再遵循双亲委派模型。
3. 反射
3.1 几个相关的类
1、java.lang.Class
,代表一个类。
2、java.lang.reflect.Method
,代表类的方法。
3、java.lang.reflect.Field
,代表类的成员变量。
4、java.lang.reflect.Constructor
,代表类的构造器。
3.2 Class 类
通过类的 Class 类可以知道这个类的:
对于每个类而言,JRE 都为其保留了一个不变的 Class 属性的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
需要注意的是:
Class 类本身也是一个类;
Class 对象只能由系统建立对象;
一个加载的类在 JVM 中只会有一个 Class 实例;
一个 Class 对象对应的是一个加载到 JVM 中的一个 .class
文件;
可以通过任意一个类的实例获取到这个类的 Class 类;
通过 Class 对象可以完整地得到某一个类中所有被加载的结构;
Class 类是反射的根源,要想进行反射,得先获取到对应的 Class 对象。
3.3 如何获取 Class 类的实例
若已知具体的类,通过类的 class
属性获取。该方法最为可靠,程序性能最高。
1 Class clazz = Person.class;
已知某个类的实例,调用该实例的 getClass()
方法获取 Class
对象。
1 Class clazz = person.getClass();
已知一个类的全限定名,且该类在类路径下,可通过 Class
类的静态方法获取 forName()
获取。这一方法可能抛出 ClassNotFoundException
异常。
1 Class clazz = Class.forName("com.yang.reflect.Person" );
内置基本数据类型可以直接用 类名.Type
:
1 Class<Integer> type = Integer.TYPE;
还可以利用 ClassLoader
:
1 2 ClassLoader classLoader = ClassLoader.getSystemClassLoader();Class<?> clazz = classLoader.loadClass("indi.mofan.domain.Person" );
拥有 Class
对象的类型:外部类、成员(成员内部类、静态嵌套类)、局部内部类、匿名内部类、接口、数组、枚举、注解、基本数据类型,void。
3.4 使用反射获取对象的信息
给定一个 Person 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.yang.reflect;public class Person { private int id; private String name; private int age; public Person () {} public Person (int id, String name, int age) { this .id = id; this .name = name; this .age = age; } public int getId () {return id;} public void setId (int id) {this .id = id;} public String getName () {return name;} public void setName (String name) {this .name = name;} public int getAge () {return age;} public void setAge (int age) {this .age = age;} @Override public String toString () { return "Person{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}' ; } }
利用反射创建 Person 对象,并调用该对象的方法、修改该对象成员变量的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Test01 { public static void main (String[] args) throws Exception { Class c1 = Class.forName("com.yang.reflect.Person" ); Person person3 = (Person) c1.newInstance(); Method setName = c1.getDeclaredMethod("setName" , String.class); setName.invoke(person3,"mofan" ); System.out.println(person3.getName()); Person person4 = (Person) c1.newInstance(); Field name = c1.getDeclaredField("name" ); if (!name.isAccessible()) { name.setAccessible(true ); } name.set(person4,"Yang_2" ); System.out.println(person4.getName()); } }
利用 isAccessible()
方法可以判断 是否关闭 Java 语言访问控制的检查 ,关闭后才能操作私有属性。当未关闭安全检测时,可以使用 setAccessible()
方法并传入 true
表示关闭检查。
在 JDK 9 之后 isAccessible()
方法被废弃,废弃原因是它的方法名称不够准确,会让人觉得此方法用于检查反射的对象是否可访问,而实际上并非如此,作为代替,引入了 canAccess()
方法。
canAccess()
方法可以接收一个 Object
类型的参数,如果判断的是一个实例对象的方法或字段,应当传入此实例对象,反之传入 null
即可。
3.5 使用反射获取注解信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package com.yang.reflect;import java.lang.annotation.*;import java.lang.reflect.Field;public class Test02 { public static void main (String[] args) throws Exception { Class c1 = Class.forName("com.yang.reflect.Student" ); Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } Tableyang tableyang = (Tableyang) c1.getAnnotation(Tableyang.class); String value = tableyang.value(); System.out.println(value); Field name = c1.getDeclaredField("name" ); Fieldyang annotation = name.getAnnotation(Fieldyang.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length()); } } @Tableyang("db_student") class Student { @Fieldyang(columnName = "db_id",type = "int",length = 10) private int id; @Fieldyang(columnName = "db_age",type = "int",length = 2) private int age; @Fieldyang(columnName = "db_name",type = "varchar",length = 8) private String name; public Student () {} public Student (int id, int age, String name) { this .id = id; this .age = age; this .name = name; } public int getId () {return id;} public void setId (int id) {this .id = id;} public int getAge () {return age;} public void setAge (int age) {this .age = age;} public String getName () {return name;} public void setName (String name) {this .name = name;} @Override public String toString () { return "Student{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}' ; } } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Tableyang{ String value () ; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface Fieldyang{ String columnName () ; String type () ; int length () ; }
运行结果如下:
1 2 3 4 5 @com.yang.reflect.Tableyang(value=db_student) db_student db_name varchar 8
3.6 Class 的各种 name
参考链接:
在 Class
中有 getName()
、getCanonicalName()
、getSimpleName()
以及 JDK8 中新增的 getTypeName()
方法。
getName()
getName()
返回的信息可以在动态加载某个类时使用。比如使用默认的 ClassLoader
调用 Class.forName()
方法来加载某个类。在某个 ClassLoader
的范围内,所有类 getName()
返回的信息唯一。
1 2 3 4 5 6 7 8 9 public String getName () { String name = this .name; return name != null ? name : initClassName(); } private transient String name;private native String initClassName () ;
getCanonicalName()
getCanonicalName()
返回的信息可以在 import
语句中使用,也能在 toString()
方法或日志操作时使用。 注意: 在一个 ClassLoader
中,getCanonicalName()
返回的信息并不能用来唯一标识一个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public String getCanonicalName () { ReflectionData<T> rd = reflectionData(); String canonicalName = rd.canonicalName; if (canonicalName == null ) { rd.canonicalName = canonicalName = getCanonicalName0(); } return canonicalName == ReflectionData.NULL_SENTINEL? null : canonicalName; } private String getCanonicalName0 () { if (isArray()) { String canonicalName = getComponentType().getCanonicalName(); if (canonicalName != null ) return canonicalName + "[]" ; else return ReflectionData.NULL_SENTINEL; } if (isHidden() || isLocalOrAnonymousClass()) return ReflectionData.NULL_SENTINEL; Class<?> enclosingClass = getEnclosingClass(); if (enclosingClass == null ) { return getName(); } else { String enclosingName = enclosingClass.getCanonicalName(); if (enclosingName == null ) return ReflectionData.NULL_SENTINEL; String simpleName = getSimpleName(); return new StringBuilder (enclosingName.length() + simpleName.length() + 1 ) .append(enclosingName) .append('.' ) .append(simpleName) .toString(); } }
isArray()
判断该 Class 对象是否为数组。
getComponentType()
返回数组中元素的 Class 对象,如果该对象不是数组,则返回 null
。
isHidden()
是 JDK15 新增的方法,用于判断一个类是否是隐藏类,像 Lambda 表达式、方法引用就是隐藏类。
isLocalOrAnonymousClass()
判断该 Class 对象是否为本地类(定义在一个代码块中的类,比如定义在方法中、静态代码块中)或匿名类。匿名类和本地类在 Java 中无法呈现出类结构,所在位置不能通过名称表示出来,因此 getCanonicalName()
返回 null
。
getEnclosingClass()
返回该 Class 对象的封装 Class 对象,如果该 Class 对象是顶级类,则返回 null
。
getSimpleName()
getSimpleName()
返回的信息可以 不精准地 来标识一个类,因此不能保证唯一性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public String getSimpleName () { ReflectionData<T> rd = reflectionData(); String simpleName = rd.simpleName; if (simpleName == null ) { rd.simpleName = simpleName = getSimpleName0(); } return simpleName; } private String getSimpleName0 () { if (isArray()) { return getComponentType().getSimpleName() + "[]" ; } String simpleName = getSimpleBinaryName(); if (simpleName == null ) { simpleName = getName(); simpleName = simpleName.substring(simpleName.lastIndexOf('.' ) + 1 ); } return simpleName; }
getSimpleBinaryName()
返回该 Class 对象的简单二进制名称。如果该 Class 对象是顶级类,则返回 null
;否则以顶级类 getName()
信息的长度为开始截取索引,截取该 Class 对象的 getName()
信息(这是 JDK 8 里的逻辑,JDK 17 中由本地方法实现):
1 2 3 4 5 6 7 8 9 10 11 private String getSimpleBinaryName () { if (isTopLevelClass()) return null ; String name = getSimpleBinaryName0(); if (name == null ) return "" ; return name; } private native String getSimpleBinaryName0 () ;
getTypeName()
getTypeName()
返回此类型名称的信息字符串,就像 toString()
一样,表示纯粹的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public String getTypeName () { if (isArray()) { try { Class<?> cl = this ; int dimensions = 0 ; do { dimensions++; cl = cl.getComponentType(); } while (cl.isArray()); return cl.getName() + "[]" .repeat(dimensions); } catch (Throwable e) { } } return getName(); }
示例对比
目标测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class GetNameTestClass { public static class Inner { } public static void fun () { class LocalClassInMethod { } Class<LocalClassInMethod> localClazz = LocalClassInMethod.class; System.out.println(localClazz.getName()); System.out.println(localClazz.getCanonicalName()); System.out.println(localClazz.getSimpleName()); System.out.println(localClazz.getTypeName()); } static { class LocalClassInStaticBlock { } Class<LocalClassInStaticBlock> localClazz = LocalClassInStaticBlock.class; System.out.println(localClazz.getName()); System.out.println(localClazz.getCanonicalName()); System.out.println(localClazz.getSimpleName()); System.out.println(localClazz.getTypeName()); } }
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @Test @SuppressWarnings({"ConstantValue", "InstantiationOfUtilityClass"}) public void testGetName () { Class<Integer> intClazz = int .class; Assertions.assertEquals("int" , intClazz.getName()); Assertions.assertEquals("int" , intClazz.getCanonicalName()); Assertions.assertEquals("int" , intClazz.getSimpleName()); Assertions.assertEquals("int" , intClazz.getTypeName()); Class<byte []> byteClazz = byte [].class; Assertions.assertEquals("[B" , byteClazz.getName()); Assertions.assertEquals("byte[]" , byteClazz.getCanonicalName()); Assertions.assertEquals("byte[]" , byteClazz.getSimpleName()); Assertions.assertEquals("byte[]" , byteClazz.getTypeName()); Class<? extends String []> stringArrayClazz = String[].class; Assertions.assertEquals("[Ljava.lang.String;" , stringArrayClazz.getName()); Assertions.assertEquals("java.lang.String[]" , stringArrayClazz.getCanonicalName()); Assertions.assertEquals("String[]" , stringArrayClazz.getSimpleName()); Assertions.assertEquals("java.lang.String[]" , stringArrayClazz.getTypeName()); Class<GetNameTestClass> clazz = GetNameTestClass.class; Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass" , clazz.getName()); Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass" , clazz.getCanonicalName()); Assertions.assertEquals("GetNameTestClass" , clazz.getSimpleName()); Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass" , clazz.getTypeName()); Class<GetNameTestClass.Inner> innerClazz = GetNameTestClass.Inner.class; Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass$Inner" , innerClazz.getName()); Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass.Inner" , innerClazz.getCanonicalName()); Assertions.assertEquals("Inner" , innerClazz.getSimpleName()); Assertions.assertEquals("indi.mofan.pojo.GetNameTestClass$Inner" , innerClazz.getTypeName()); Class<? extends Runnable > runnableClazz = new Runnable () { @Override public void run () { } }.getClass(); Assertions.assertEquals("indi.mofan.reflection.JavaReflectionUtilTest$1" , runnableClazz.getName()); Assertions.assertNull(runnableClazz.getCanonicalName()); Assertions.assertEquals("" , runnableClazz.getSimpleName()); Assertions.assertEquals("indi.mofan.reflection.JavaReflectionUtilTest$1" , runnableClazz.getTypeName()); System.out.println("静态代码块本地类: " ); GetNameTestClass obj = new GetNameTestClass (); System.out.println("静态方法中的本地类: " ); GetNameTestClass.fun(); System.out.println("Lambda 表达式: " ); Supplier<String> supplier = () -> "Function" ; var supplierClazz = supplier.getClass(); System.out.println(supplierClazz.getName()); System.out.println(supplierClazz.getCanonicalName()); System.out.println(supplierClazz.getSimpleName()); System.out.println(supplierClazz.getTypeName()); System.out.println("方法引用: " ); Function<Integer, String> fun = String::valueOf; var funClazz = fun.getClass(); System.out.println(funClazz.getName()); System.out.println(funClazz.getCanonicalName()); System.out.println(funClazz.getSimpleName()); System.out.println(funClazz.getTypeName()); }
运行测试方法后,测试通过,控制台打印出:
静态代码块本地类:
indi.mofan.pojo.GetNameTestClass$1LocalClassInStaticBlock
null
LocalClassInStaticBlock
indi.mofan.pojo.GetNameTestClass$1LocalClassInStaticBlock
静态方法中的本地类:
indi.mofan.pojo.GetNameTestClass$1LocalClassInMethod
null
LocalClassInMethod
indi.mofan.pojo.GetNameTestClass$1LocalClassInMethod
Lambda 表达式:
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
null
JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$356/0x0000000800ca8c58
方法引用:
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78
null
JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78
indi.mofan.reflection.JavaReflectionUtilTest$$Lambda$357/0x0000000800ca8e78
3.7 获取类的泛型信息
getGenericSuperclass()
getGenericSuperclass()
方法用于获取含有泛型信息的父类,如果父类不含泛型信息,该方法等价于 getSuperclass()
方法。
在测试类 GetGenericInfoTest
中有这样两个静态嵌套类:
1 2 3 4 5 static class MyList extends ArrayList <String> {} static class MyLinkList <T> extends LinkedList <T> {}
分别获取 MyList
和 MyLinkList
含有泛型信息的父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test public void testGetGenericSuperclass () { Type genericSuperclass = MyList.class.getGenericSuperclass(); Assert.assertEquals("java.util.ArrayList<java.lang.String>" , genericSuperclass.toString()); Assert.assertTrue(genericSuperclass instanceof ParameterizedType); ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass; Assert.assertArrayEquals(new Type []{String.class}, parameterizedType.getActualTypeArguments()); Assert.assertEquals(ArrayList.class, parameterizedType.getRawType()); Assert.assertNull(parameterizedType.getOwnerType()); Assert.assertEquals("java.util.ArrayList<java.lang.String>" , parameterizedType.getTypeName()); genericSuperclass = MyLinkList.class.getGenericSuperclass(); Assert.assertEquals("java.util.LinkedList<T>" , genericSuperclass.toString()); Assert.assertTrue(genericSuperclass instanceof ParameterizedType); parameterizedType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Assert.assertEquals(1 , actualTypeArguments.length); Assert.assertEquals("T" , actualTypeArguments[0 ].toString()); Assert.assertEquals(LinkedList.class, parameterizedType.getRawType()); Assert.assertNull(parameterizedType.getOwnerType()); Assert.assertEquals("java.util.LinkedList<T>" , parameterizedType.getTypeName()); }
对 MyList
来说,其父类是 ArrayList
,不含泛型参数,其泛型是确切的 String
类型,因此 ParameterizedType
对象的 getActualTypeArguments()
方法返回的是含有 String.class
的数组。由于 ArrayList
不是某个类的嵌套类,因此 getOwnerType()
方法的返回结果是 null
。
对 MyLinkList
来说,其父类是 LinkedList
,含有泛型参数 T
,因此 ParameterizedType
对象的 getActualTypeArguments()
方法返回的信息中含有泛型参数 T
。LinkedList
也不是某个类的嵌套类,getOwnerType()
方法的返回结果也是 null
。
如果要获取实现的接口的泛型信息呢?还可以使用 getGenericSuperclass()
方法吗?
比如在测试类 GetGenericInfoTest
中有以下接口和嵌套类:
1 2 3 4 5 6 7 8 interface MyInterface <T> {} static class MyInterfaceImpl_1 implements MyInterface <String> {} static class MyInterfaceImpl_2 <T> implements MyInterface <T> {}
尝试使用 getGenericSuperclass()
方法获取 MyInterfaceImpl_1
和 MyInterfaceImpl_2
的实现的接口的泛型信息:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testGetGenericSuperclass_2 () { Type genericSuperclass = MyInterface.class.getGenericSuperclass(); Assert.assertNull(genericSuperclass); genericSuperclass = MyInterfaceImpl_1.class.getGenericSuperclass(); Assert.assertFalse(genericSuperclass instanceof ParameterizedType); genericSuperclass = MyInterfaceImpl_2.class.getGenericSuperclass(); Assert.assertFalse(genericSuperclass instanceof ParameterizedType); }
很遗憾,使用 getGenericSuperclass()
方法并不能成功获取,获取父接口的泛型信息可以使用 getGenericInterfaces()
方法。
getGenericInterfaces()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test public void testGetGenericInterfaces () { Type[] genericInterfaces = MyInterface.class.getGenericInterfaces(); Assert.assertEquals(0 , genericInterfaces.length); genericInterfaces = MyInterfaceImpl_1.class.getGenericInterfaces(); Assert.assertEquals(1 , genericInterfaces.length); for (Type genericInterface : genericInterfaces) { Assert.assertTrue(genericInterface instanceof ParameterizedType); ParameterizedType type = (ParameterizedType) genericInterface; Assert.assertArrayEquals(new Type []{String.class}, type.getActualTypeArguments()); Assert.assertEquals(MyInterface.class, type.getRawType()); Assert.assertEquals(this .getClass(), type.getOwnerType()); } genericInterfaces = MyInterfaceImpl_2.class.getGenericInterfaces(); Assert.assertEquals(1 , genericInterfaces.length); for (Type genericInterface : genericInterfaces) { Assert.assertTrue(genericInterface instanceof ParameterizedType); ParameterizedType type = (ParameterizedType) genericInterface; Assert.assertEquals("T" , type.getActualTypeArguments()[0 ].toString()); Assert.assertEquals(MyInterface.class, type.getRawType()); Assert.assertEquals(this .getClass(), type.getOwnerType()); } }
Java 不允许多继承,但允许实现多个接口,因此 getGenericInterfaces()
返回的是一个数组,表示实现的多个接口信息。
在返回的 Type
数组中,如果实现的某个接口带有泛型信息,可以将 Type
对象转换为 ParameterizedType
对象来获取泛型信息。
MyInterface
接口是定义在测试类中的嵌套类,因此调用 getOwnerType()
方法返回的是当前测试类的 Class
对象。
只能使用 getGenericInterfaces()
方法来获取实现的接口的泛型信息,而不能获取继承的父类的泛型信息,否则最终得到的 Type
数组是一个空数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static class MyClass <T> {} static class SimpleClass extends MyClass <String> {} @Test public void testGetGenericInterfaces_2 () { Type[] genericInterfaces = MyClass.class.getGenericInterfaces(); Assert.assertEquals(0 , genericInterfaces.length); genericInterfaces = SimpleClass.class.getGenericInterfaces(); Assert.assertEquals(0 , genericInterfaces.length); }
需要获取其他位置的泛型信息时,参考:JAVA反射 | 泛型解析
3.8 反射调用可变参数方法
可变参数可以当成对应的数组类型参数。
如果可变参数类型是引用类型:接收到参数后,会自动拆包取出参数再分配给底层方法,因此需要将传入的数组包装成 Object
对象或者将其作为 Object[]
中的一个元素;
如果可变参数类型是基本类型:不会将参数拆包,因此可以不用包装,但包装了也不会抛出异常,为了统一,可以和引用类型的可变参数一样,都包装一层。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static class MyClass { public void firstMethod (int ... ints) { System.out.println("call firstMethod" ); } public void secondMethod (String... strings) { System.out.println("call secondMethod" ); } } @Test @SneakyThrows @SuppressWarnings("all") public void testVaryArgsMethod () { MyClass obj = new MyClass (); Class<MyClass> clazz = MyClass.class; Method firstMethod = clazz.getDeclaredMethod("firstMethod" , int [].class); firstMethod.invoke(obj, new int []{1 , 2 , 3 }); firstMethod.invoke(obj, (Object) new int []{1 , 2 , 3 }); firstMethod.invoke(obj, new Object []{new int []{1 , 2 , 3 }}); Method secondMethod = clazz.getDeclaredMethod("secondMethod" , String[].class); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> secondMethod.invoke(obj, new String []{"a" , "b" , "c" })) .withMessage("wrong number of arguments" ); secondMethod.invoke(obj, (Object) new String []{"a" , "b" , "c" }); secondMethod.invoke(obj, new Object []{new String []{"a" , "b" , "c" }}); }