本文是面向初学者的反射入门教程。主要内容是在运行时获取类的构造方法、成员方法、成员变量。文章侧重于怎么做(代码怎么写),弱化对反射内部原理的解释说明。
目录
什么是反射
反射的定义:允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。通过 Class类 获取 某类的字节码信息称之为反射(Reflection)。
反射的功能:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
反射的前提:
- 被反射的类,一定要有空参数的构造方法;
- 构造方法权限必须是public;
JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。并且调用该类的方法和属性。
反射的实现主要借助以下四个类:
- Class:代表任何的类
- Constructor:代表类的构造方法
- Field:代表类中的属性
- Method:代表类中的方法
类加载器
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
类的加载过程:
- 加载
- 就是指将class文件读入内存,并为之创建一个Class对象。
- 任何类被使用时系统都会建立一个Class对象
- 连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化
- 创建类的实例
- 类的静态变量,或者为静态变量赋值
- 类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
类加载器的组成
- Bootstrap ClassLoader 根类加载器
- 也被称为引导类加载器,负责Java核心类的加载,最基础的文件加载
- 比如System,String等。在JDK中JRE/lib/rt.jar文件中
- Extension ClassLoader 扩展类加载器
- 负责JRE的扩展目录中jar包的加载,也即加载JRE/lib/ext/*jar
- 在JDK中JRE的lib目录下ext目录
- System ClassLoader 系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。例如,在MySQL的驱动的时候,就是在系统类加载器中实现加载的。
通过这些描述就可以知道我们常用的类,都是由谁来加载完成的。到目前为止我们已经知道把class文件加载到内存了,那么,如果我们仅仅站在这些class文件的角度,我们如何来使用这些class文件中的内容呢?这就是我们反射要研究的内容。
如何获得类加载器:
获得该类的字节码对象:Class c = 类名.class;
获得类加载器:ClassLoader cl = c.getClassLoader();
获得该类下的所有资源:getResource(“资源名”);
Class类
什么是Class类:
- 当JVM装载了字节码文件(即.class文件)后,会把每一个类都封装到Class类实例中,每一个类都对应着一个Class实例。
- Class类实例(即Class对象)中保存的是创建类的一些列数据信息,比如创建一个HelloWorld类,那么JVM会生成一个内容是HelloWorld的Class类对象。
- Class类的作用是运行时提供或获得某个对象的类型信息,和C++中的typeid()函数类似。这些信息也可用于反射。
特性:
- Class 没有公共构造方法。即不能程序员创建。
- Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
- 所在位置:java.lang.Class<T>
获取Class对象的三种方式:
- 对象获取
类名 变量名1 = new 类名();
Class 变量名2 = 变量名1.getClass(); - 类名获取
Class 变量名 = 类名.class; - Class类的静态方法获取
Class 变量名 = Class.forName(字符串的类名,即包名和类名要一起);(注意需要抛出异常ClassNotFoundException)
注意:三种方式获取到的Class对象相等!
如何通过反射创建对象?
- 方案一:获取Class类对象,调用newInstance()方法,注意这种方式只能生成object类的实例
- 方案二:获取Class类对象,获取构造器,由构造器调用newInstance()方法
通过反射快速创建类实例
//前提:①必须又有空构造方法; ②构造方法权限必须是public
package kyle;
import entity.Person;
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("entity.Person");
Object obj = c.newInstance();
System.out.println(obj.toString());
Constructor<Person> con = c.getConstructor(String.class,Integer.class,String.class);
Person person = con.newInstance("kyle",22,"man");
System.out.println(person.toString());
}
}
/*
输出:
Person{name='null', age=null, sex='null'}
Person{name='kyle', age=22, sex='man'}
*/
Constructor类
java.lang.reflect.Constructor<T>
获取构造器:
public构造器
- Constructor getConstructor()
- Constructor[] getConstructors()
- Constructor<T> getConstructor(Class<?>... parameterTypes) 获取指定参数名的构造器。例如Constructor con = c.getConstructor(String.class,int.class);就是获取参数为String和int型的构造函数
所有(含private)构造器
- Constructor[] getDeclaredConstructors() 获取全部构造器,即使是private的构造器
- Constructor getDeclaredConstructor(Class... parameterTypes) 获取指定参数的构造器,即使是private的构造器
操作构造器:
- void setAccessible(true) 修改构造器访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
- T newInstance(Object… initargs) 调用构造器传入初始化参数,创建对象
获取public的构造器
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("kyle.Person"); //获取kyle包下的Person类
// Constructor[] getConstructors() 获取class文件对象中的所有公共的构造方法
Constructor[] cons = c.getConstructors();
for (Constructor con : cons) {
System.out.println(con);
}
/*输出
public kyle.Person(java.lang.String,int)
public kyle.Person()
*/
//获取空参数的构造方法
Constructor con = c.getConstructor();
System.out.println(con); //输出:public kyle.Person()
//使用空参构造器创建类实例
Object obj = con.newInstance();
System.out.println(obj.toString()); //输出:Person [name=null, age=0]
}
}
获取public有参构造器
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("kyle.Person");
//获取带有指定参数的构造方法
//Constructor<T> getConstructor(Class<?>... parameterTypes)
//Class<?>... parameterTypes 传递要获取的构造方法的参数列表
Constructor con = c.getConstructor(String.class,int.class);
//使用该构造器创建实例
// T newInstance(Object... initargs)
//Object... initargs 运行构造方法后,传递的实际参数
Object obj = con.newInstance("张三",20);
System.out.println(obj);
}
}
//输出;Person [name=张三, age=20]
获取私有的构造方法
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("kyle.Person");
//Constructor[] getDeclaredConstructors()获取所有的构造方法,包括私有的
Constructor[] cons = c.getDeclaredConstructors();
for(Constructor con : cons){
System.out.println(con);
}
/*输出:
private kyle.Person(int,java.lang.String)
public kyle.Person(java.lang.String,int)
public kyle.Person()
*/
//Constructor getDeclaredConstructor(Class...c)获取到指定参数列表的构造方法
Constructor con = c.getDeclaredConstructor(int.class, String.class);
// Constructor类的父类中有一个父类AccessibleObject
// 该类有个方法setAccessible(boolean b),可以实现私有的变量和方法的获取
con.setAccessible(true);
Object obj = con.newInstance(18, "lisi");
System.out.println(obj);
}
}
Field类
java.lang.reflect.Field
public成员变量:
- Field getField(String name);
- Field [] getFields();
全部(含private、常量、静态)成员变量:
- Field getDeclaredField(String name);
- Field[] getDeclaredFields();
操作成员变量:
- void set(Object obj, Object value):给对象注入某个成员变量数据
- Object get(Object obj):获取对象的成员变量的值。
- void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
- Class getType(); 获取属性的类型,返回Class对象。
- String getName(); 获取属性的名称。
获取public成员变量
import java.lang.reflect.Field;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("kyle.Person");
// Class类的方法:getFields()可以获取被反射类中的所有public成员变量
// 返回值是Field[],其中Field类描述成员变量对象的类
Field[] fields = c.getFields();
for(Field f : fields){
System.out.println(f);
} //输出:public java.lang.String kyle.Person.name
//获取指定的成员变量: Field getField(传递字符串类型的变量名)
Field field = c.getField("name");
//修改成员变量的值:void set(Object obj, Object value)
//Object obj 必须有对象的支持, Object value 修改后的值
Object obj = c.newInstance();
field.set(obj,"王五");
System.out.println(obj); //输出:Person [name=王五, age=0]
}
}
Method类
java.lang.reflect.Method
获取public方法:
- public Method[] getMethods() throws SecurityException; 返回某个类的所有公用(public)方法,包括其继承类的公用方法。
- public Method getMethod(String name, Class<?>... parameterTypes);
获取全部(含private)方法:
- public Method[] getDeclaredMethods() throws SecurityException; 返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
- Method getDeclaredMethod(String name, Class<?>... parameterTypes);
操作方法:
Object invoke(Object obj, Object... args) 参数一:触发的是哪个对象的方法执行。参数二: args:调用方法时传递的实际参数
获取并执行成员方法
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Exception {
Class c = Class.forName("kyle.Person");
//获取被反射类中的成员方法:Method[] getMethods()获取的是class文件中的所有公共成员方法,包括继承的
Method[] methods = c.getMethods();
for(Method m : methods){
System.out.println(m);
}
//获取指定的方法:Method getMethod(String methodName,Class...c)
Method method = c.getMethod("eat"); //获取“eat”方法
//运行获取到的方法
Object obj = c.newInstance();
//Object invoke(Object obj, Object...o)
method.invoke(obj);
}
}
总结:
- 获取Class:
- Class c = new 类名().getClass();
- Class c = 类.class;
- Class c = Class.forName("类全名");
- 获取构造方法Constructor:getConstructor、getConstructors、getDeclaredConstructor
- 获取成员变量 Field:getField、getDeclaredField
- 获取成员方法Method:getMethod、getDeclaredMethod