深入理解Class类型
RTTI
RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息。大致分为两种:
- 传统的”RRTI”,它假定我们在编译期已知道了所有类型。
- 另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。
在Java中用来表示运行时类型信息的对应类就是Class类。
Class类被创建后的对象就是Class对象,注意,Class对象表示的是自己手动编写类的类型信息,比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,该Class对象保存了Shapes类相关的类型信息。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里(编译后的字节码文件保存的就是Class对象),那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象,
Class对象加载及其获取方式
Class对象的加载
前面我们已提到过,Class对象是由JVM加载的,那么其加载时机是?实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类(实际上加载的就是这个类的字节码文件),注意,使用new操作符创建类的新实例对象也会被当作对类的静态成员的引用(构造函数也是类的静态方法),由此看来Java程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载,所以在使用该类时,类加载器首先会检查这个类的Class对象是否已被加载(类的实例对象创建时依据Class对象中类型信息完成的),如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。下面通过一个简单例子来说明Class对象被加载的时机问题。
Class.forName()
Class.forName()方法的调用将会返回一个对应类的Class对象,想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象。当然还可以通过obj.getClass()、Obj.class(通过该方式获取不会自动初始化该类,因此效率更高)获取。
类加载过程
- 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
- 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。
- 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
- 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段)。
- 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要。
- 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
- 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类。
- 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化。
理解泛化的Class对象引用
在引入泛型后,可以利用泛型来表示Class对象更具体的类。Class<Integer> i1 = int.class;
Class i2 = int .class;
关于类型转换的问题
归功于RRTI,所有类型转换都是在运行时进行正确性检查的,利用RRTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出类型转换异常。java也提供了Class对象转换的方式:int a = 0;
Class<Integer> i1 = int.class;
Integer i2 = i1.cast(a);
instanceof 关键字与isInstance方法
- instanceof:关键字,用于判断对象是不是某个特定类型的实例。
- isInstance:Class类中的一个Native方法,也是用于判断对象类型。
深入理解反射技术
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
static Class<?> | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
Constructor |
getConstructor(Class<?>… parameterTypes) | 返回指定参数类型、具有public访问权限的构造函数对象 |
Constructor<?>[] | getConstructors() | 返回所有具有public访问权限的构造函数的Constructor对象数组 |
Constructor |
getDeclaredConstructor(Class<?>… parameterTypes) | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
Constructor<?>[] | getDeclaredConstructors() | 返回所有声明的(包括private)构造函数对象 |
T | newInstance() | 创建此 Class 对象所表示的类的一个新实例。 |
以下是关于Constructor类本身的以下常用函数:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Class |
getDeclaringClass() | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) |
Type[] | getGenericParameterTypes() | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 |
String | getName() | 以字符串形式返回此构造方法的名称。 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 |
T | newInstance(Object… initargs) | 使用此 Constructor对象表示的构造函数来创建新实例 |
String | toGenericString() | 返回描述此 Constructor 的字符串,其中包括类型参数。 |
void | setAccessible(true or false) | 访问private时必须设置可访问 |
Type
Type 是 Java 编程语言中所有类型的公共高级接口。包括原始类型、参数化类型、数组类型、类型变量和基本类型。getGenericParameterTypes 与 getParameterTypes 都是获取构成函数的参数类型,前者返回的是Type类型,后者返回的是Class类型,由于Type顶级接口,Class也实现了该接口,因此Class类是Type的子类,Type 表示的全部类型而每个Class对象表示一个具体类型的实例,如String.class仅代表String类型。由此看来Type与 Class 表示类型几乎是相同的,只不过 Type表示的范围比Class要广得多而已。
- TypeVariable:表示类型参数,可以有上界,比如:T extends Number
- ParameterizedType:表示参数化的类型,有原始类型和具体的类型参数,比如:List
- WildcardType:表示通配符类型,比如:?, ? extends Number, ? super Integer
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。Class类与Field对象相关方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field | getDeclaredField(String name) | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] | getDeclaredField() | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
Field | getField(String name) | 获取指定name名称、具有public修饰的字段,包含继承字段 |
Field[] | getField() | 获取修饰符为public的字段,包含继承字段 |
如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。
Field类还有其他常用的方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
void | set(Object obj, Object value) | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 |
Object | get(Object obj) | 返回指定对象上此 Field 表示的字段的值 |
Class<?> | getType() | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 |
boolean | isEnumConstant() | 如果此字段表示枚举类型的元素则返回 true;否则返回 false |
String | toGenericString() | 返回一个描述此 Field(包括其一般类型)的字符串 |
String | getName() | 返回此 Field 对象表示的字段的名称 |
Class<?> | getDeclaringClass() | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 |
void | setAccessible(boolean flag) | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。下面是Class类获取Method对象相关的方法:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method | getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] | getDeclaredMethod() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method | getMethod(String name, Class<?>… parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] | getMethods() | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
在通过getMethods方法获取Method对象时,会把父类的方法也获取到。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。
Method提供的功能如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Object | invoke(Object obj, Object… args) | 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 |
Class<?> | getReturnType() | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 |
Type | getGenericReturnType() | 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。 |
Class<?>[] | getParameterTypes() | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 |
Type[] | getGenericParameterTypes() | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 |
String | getName() | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
boolean | isVarArgs() | 判断方法是否带可变参数,如果将此方法声明为带可变数量的参数,则返回 true;否则,返回 false。 |
String | toGenericString() | 返回描述此 Method 的字符串,包括类型参数。 |
getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息:public interface Type {
//1.8新增
default String getTypeName() {
return toString();
}
}
而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。
反射包中的Array类
在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array,它提供了动态创建和访问 Java 数组的方法。Array 允许在执行 get 或 set 操作进行取值和赋值。在Class类中与数组关联的方法是:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Class<?> | getComponentType() | 返回表示数组元素类型的 Class,即数组的类型 |
boolean | isArray() | 判定此 Class 对象是否表示一个数组类。 |
java.lang.reflect.Array中的常用静态方法如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
static Object | set(Object array, int index) | 返回指定数组对象中索引组件的值。 |
static int | getLength(Object array) | 以 int 形式返回指定数组对象的长度 |
static object | newInstance(Class<?> componentType, int… dimensions) | 创建一个具有指定类型和维度的新数组。 |
static Object | newInstance(Class<?> componentType, int length) | 创建一个具有指定的组件类型和长度的新数组。 |
static void | set(Object array, int index, Object value) | 将指定数组对象中索引组件的值设置为指定的新值。 |
其实除了上的set/get外Array还专门为8种基本数据类型提供特有的方法,如setInt/getInt、setBoolean/getBoolean。
其他功能方法
修饰符、父类、实现的接口、注解相关
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
int | getModifiers() | 获取修饰符 |
Class<? super T> | getSuperclass() | 获取父类,如果为Object,父类为null |
Class<?>[] | getInterfaces() | 对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的 |
Annotation[] | getDeclaredAnnotations() | 声明的注解 |
Annotation[] | getAnnotations() | 所有的注解,包括继承得到的 |
<A extends Annotation> A | getAnnotation(Class<A> annotationClass) | 获取或检查指定类型的注解,包括继承得到的 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 获取或检查指定类型的注解,包括继承得到的 |
内部类相关
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Class<?>[] | getClasses() | 获取所有的public的内部类和接口,包括从父类继承得到的 |
Class<?>[] | getDeclaredClasses() | 获取自己声明的所有的内部类和接口 |
Class<?> | getDeclaringClass() | 如果当前Class为内部类,获取声明该类的最外部的Class对象 |
Class<?> | getEnclosingClass() | 如果当前Class为内部类,获取直接包含该类的类 |
Method | getEnclosingMethod() | 如果当前Class为本地类或匿名内部类,返回包含它的方法 |
Class对象类型判断相关
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
boolean | isArray() | 是否是数组 |
boolean | isPrimitive() | 是否是基本类型 |
boolean | isInterface() | 是否是接口 |
boolean | isEnum() | 是否是枚举 |
boolean | isAnnotation() | 是否是注解 |
boolean | isAnonymousClass() | 是否是匿名内部类 |
boolean | isMemberClass() | 是否是成员类 |
boolean | isLocalClass() | 是否是本地类 |