一 反射前述
1.1 Reflect
1)反射机制(Reflection)被视为动态语言的关键,它允许程序在执行过程中借助于Reflection Api 获取任意类的内部信息,并且可以直接操作任意对象的内部属性和方法。
2)类被加载完之后,在堆内存中会产生一个Class类的对象(一个类只有一个Class类对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象来看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
1.2 动态语言和静态语言
1)动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C、C#、PHP、JavaScript、Python、Erlang
2)静态语言:与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++。
3)Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
1.3 Java反射机制提供的功能和应用
1)在运行时判断任意一个对象所属的类
2)在运行时构造任意一个类的对象
3)在运行时判断任意一个类所具有的成员变量和方法
4)在运行时获取泛型信息
5)在运行时调用任意一个对象的成员变量和方法
6)在运行时处理注解
7)生成动态代理
1.4 Java反射的相关API
1)java.lang.Class:代表一个类
2)java.lang.reflect.Method:代表类的方法
3)java.lang.reflect.Field:代表类的成员变量
4)java.lang.reflect.Constructor:代表类的构造器
二 Class类和获取Class实例
2.1 Class类
1)Object类方法
- 这个方法被所有的子类继承,因此所有的类都有这个方法,返回当前的一个Class对象。
2)Class类:是Java反射的源头,对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构 ( class / interface / enum / annotation / primitive type / void / [ ] )的有关信息。public final Class getClass();
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
3)Class类常用的方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() | 返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) | 返回一个Method对象,此对象的形参类型为paramType |
4)Method 类的常用方法
// 取得全部的返回值
public Class<?> getReturnType()
// 取得全部的参数
public Class<?>[] getParameterTypes()
// 取得修饰符
public int getModifiers()
// 取得异常信息
public Class<?>[] getExceptionTypes()
5)Constructor 类常用的方法
// 取得修饰符
public int getModifiers();
// 取得方法名称
public String getName();
// 取得参数的类型
public Class<?>[] getParameterTypes();
6)Field 类常用的方法
// 以整数形式返回此Field的修饰符
public int getModifiers()
// 得到Field的属性类型
public Class<?> getType()
// 返回Field的名称
public String getName()
2.2 获取Class类实例的四种方式
1)如果已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高
Class clazz = String.class;
2)已知某个类的实例,调用getClass() 方法获取Class类对象
Class clazz = "AA".getClass();
3)已知一个类的全类名,且类在类路径下
Class clazz = Class.forName("java.lang.String");
4)ClassLoader类加载器
需要注意,Java的不同类加载器加载不同的类
ClassLoader cl = this.getClass().getClassLoader();
Class clazz = cl.loadClass("java.lang.String");
2.3 哪些类型可以拥有字节码对象
①class:外部类,成员类,局部内部类,匿名内部类
②interface:接口
③[ ] :数组
④enum:枚举类
⑤annotation:注解
⑥primitive type:基本数据类型
⑦void
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11); // true
三 类加载器和类的加载
3.1 类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
1)Load:类的加载
- 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
2)Link:类的链接
- 将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
3)Initialize:类的初始化
- JVM负责对类进行初始化。
- 执行类构造器
()方法的过程。类构造器 ()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。 - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的
()方法在多线程环境中被正确加锁和同步。
3.2 不同类型的类加载器
1)引导类加载器(boostrap classloader)
- 负责加载Java核心代码的类库,比如 /jre/lib/rt.jar,/resource.jar等,这些包下的类都是被引导类加载器所加载的
- 是由C/C++来实现的,镶嵌在Java虚拟机中,所以并不继承java中的java.lang.ClassLoader类,没有父类加载器
- 额外负责加载扩展类加载器和系统类加载器类,并指定为它们的父类加载器
- 处于安全考虑,引导类加载器只会加载开头为java,javax,sun开头的类
2)扩展类加载器(extension classloader)
- 负责加载jre包下的扩展类,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包
- Java实现:sun.misc.Launcher$ExtClassLoader
3)系统类加载器(system classloader)
- 负责加载开发者编写的类和第三方的jar包
- Java实现:sun.misc.Launcher$AppClassLoader
四 创建运行时类的对象
可以调用Class对象的newInstance()方法
1)注意:
①类须有一个无参数的构造器
②类的构造器权限需要足够大
2)如果没有无参的构造器也是可以创建运行时类的对象的,只是需要开发者指定提供参数。
4.1 方式一
①获取运行时类对应的Class类对象
String name = “com.lz.Person";
Class clazz = Class.forName(name);
②调用运行时类指定参数结构的构造器,获取一个Constructor 类实例
// 返回运行时类的有参构造器对象
// 假如 Person 类没有公共可访问的构造器,可以获取其 Constructor 实例来创建 Person 实例
Constructor con = clazz.getConstructor(String.class,Integer.class);
③通过Constructor对象来创建运行时类的实例
Person p = (Person) con.newInstance("Peter",20);
System.out.println(p);
4.2 方式二
- Class类的newInstance()方法
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance(); // Class类的newInstance方法
person.setAge(1);
person.setName("lz");
System.out.println(person);
}
五 获取运行时类的完整结构
5.1 相关结构
1)Field
2)Method
3)Constructor
4)Superclass
5)Interface
6)Annotation
在实际的操作中,取得类的信息的操作代码,并不会经常开发。一定要熟悉java.lang.reflect包的作用,反射机制。学会如何取得属性、方法、构造器的名称,修饰符等。
5.2 获取结构
5.2.1 获取运行时类实现的全部接口
确定此对象所表示的类或接口实现的接口
// Class类提供的方法
public Class<?>[] getInterfaces()
5.2.2 获取运行时类的直接父类
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class
public Class<? Super T> getSuperclass()
5.2.3 获取运行时类的全部构造器
// 返回运行时类的所有public构造器
public Constructor<T>[] getConstructors()
// 返回运行时类声明的所有构造器
public Constructor<T>[] getDeclaredConstructors()
5.2.4 获取运行时类所有的方法
// 获取运行时类的所有方法
public Method[] getDeclaredMethods()
// 获取Class类对象表示的类或接口的所有public方法
public Method[] getMethods()
// 根据方法名,参数获取其中一个方法
public Method getMethod(String name,Class…parameterTypes)
5.2.5 获取Class类对象表示的类或接口的所有属性
// 返回此Class对象所表示的类或接口的public的Field。
public Field[] getFields()
// 返回此Class对象所表示的类或接口的全部Field
public Field[] getDeclaredFields()
5.2.6 注解
public Annotation[] getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotations()
5.2.7 泛型
// 获取父类泛型类型
Type getGenericSuperclass()
// 泛型类型
ParameterizedType
// 获取实际的泛型类型参数数组
getActualTypeArguments()
5.2.8 获取运行时类的包
public Package getPackage()
六 调用运行时类指定的结构
6.1 调用方法
1)先通过 getMethod(String name,Class…parameterTypes) 方法获取一个Method 对象,然后调用 Object invoke(Object obj, Object[] args),向方法中传递要设置的obj对象的参数信息。
2)然后使用 Method类中的 invoke(Object,Object[]) 方法来调用实例对象的方法
3)invoke方法说明
- Object 对应原方法的返回值,若原方法无返回值,此时返回 null。
- 若原方法若为静态方法,那么第一个参数obj 可以是Class类的对象,也可以是运行时类的对象。
- 若原方法若为普通方法,那么第一个参数obj 只能是是运行时类的对象。
- 若原方法形参列表为空,则第二个参数 args 为 null。
若原方法声明为private,则需要在调用此invoke()方法前,显式调用Method对象的 setAccessible(true) 方法,将可访问private的方法。
// 反射调用普通方法,invoke的第一个参数为运行时类对象
@Test
public void test() throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Method sayHi = clazz.getMethod("sayHi", null);
sayHi.setAccessible(true);
String result = (String) sayHi.invoke(person, null);
System.out.println("反射调用方法成功 " + result);
}
调用静态方法
// 反射调用静态方法,invoke的第一个参数为Class 类对象
@Test
public void test1() throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Method staticMethod = clazz.getMethod("staticMethod", null);
staticMethod.invoke(clazz, null);
}
6.2 调用指定的属性
1)反射机制中,可以直接通过 Field 类操作类中的属性,通过Field类提供的 set() 和 get() 方法就可以完成设置和取得属性内容的操作。
// 返回此Class对象表示的类或接口的指定的public的Field。
public Field getField(String name)
// 返回此Class对象表示的类或接口的指定的Field
public Field getDeclaredField(String name)
2)在Field中
// 取得指定对象obj上此Field的属性内容
public Object get(Object obj)
// 设置指定对象obj上此Field的属性内容
public void set(Object obj, Object value)
6.3 关于setAccessible方法的使用
1)Method、Field、Constructor对象都有setAccessible()方法。
2)setAccessible启动和禁用访问安全检查的开关。
3)参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问
4)参数值为false则指示反射的对象应该实施Java语言访问检查