JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。并且调用该类的方法和属性。反射机制有这广泛的用途,所以,别想那么多,学就是了。
反射的定义: 允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。通过 Class类 获取 某类的字节码信息称之为反射(
Reflection
)。
反射的功能:
- 对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
反射的前提:
- 被反射的类,一定要有空参数的构造方法;
- 构造方法权限必须是public;
反射的实现主要借助以下四个类:
Class
:类的对象,Constructor
:类的构造方法,Field
:类中的属性对象,Method
:类中的方法对象。
Java程序是怎么运行起来的
- 你先写好了一个Java源程序,没有错误后,编译;
- 然后你要准备开始运行该程序,但是该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
运行三步走:
- 加载
- 就是指将class文件读入内存,并为之创建一个Class对象。
- 任何类被使用时系统都会建立一个Class对象
- 连接
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化
- 创建类的实例
- 类的静态变量,或者为静态变量赋值
- 类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
以上的第一步加载的过程中,有一个类加载器,专门用于加载字节码文件到内存,具体一点是JVM。以下是整个程序运行的过程示意图:
类加载器的组成
- 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
:类的对象,Constructor
:类的构造方法,Field
:类中的属性对象,Method
:类中的方法对象。
Class-获取类对象
特性:
- Class 没有公共构造方法。即
不能程序员创建
。 - Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法
自动构造
的。 - 所在位置:java.lang.Class
获取Class对象的三种方式:
- 对象获取
类名 变量名1 = new 类名();
Class 变量名2 = 变量名1.getClass();
- 类名获取
Class 变量名 = 类名.class;
- Class类的静态方法获取
Class 变量名 = Class.forName(字符串的类名,即包名和类名要一起);
(注意需要抛出异常ClassNotFoundException)
注意:三种方式获取到的Class对象相等!
Constructor-获取类构造方法
**获取构造方法:**由Class类的对象调用
-
获取class文件对象中的所有公共的构造方法:
Constructor[] getConstructors()
-
获取无参数的构造方法:
Constructor getConstructor()
-
获取带有指定参数的构造方法:
Constructor<T> getConstructor(Class<?>... parameterTypes)
,参数表名要调用哪个构造函数;例如:Constructor con = c.getConstructor(String.class,int.class);就是获取参数为String和int型的构造函数 -
获取所有的构造方法,包括私有的:
Constructor[] getDeclaredConstructors()
-
获取到指定参数列表的构造方法
Constructor getDeclaredConstructor(Class...c);
,例如:Constructor con = c.getDeclaredConstructor(int.class, String.class);
获取公共的构造方法
package kyle;
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]
}
}
获取公共的指定参数的构造方法
package kyle;
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-获取类成员变量
获取方法:由Class类对象调用
-
访问公有的指定成员变量:
Filed getFiled(String name);
-
获取所有公有的成员变量:
Filed[] getFileds();
-
获取所有已声明的成员变量(含私有),但不能得到其父类的成员变量:
Filed getDeclaredField(String name);
-
获取所有的成员变量(私有):
Filed[] getDeclaredFields();
操作方法:
- 通过类对象的getDeclaredField()方法字段(Field)对象;
- 然后再通过字段对象的setAccessible(true)将其设置为可以访问;
- 接下来就可以通过get/set方法来获取/设置字段的值了。
获取公共成员变量
package kyle;
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-获取类方法
获取方法:
-
public Method[] getDeclaredMethods() throws SecurityException;
方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 -
Method getDeclaredMethod(String name, Class<?>... parameterTypes);
返回指定的参数的方法; -
public Method[] getMethods() throws SecurityException;
方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。 -
public Method getMethod(String name, Class<?>... parameterTypes);
方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
获取并执行成员方法
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);
}
}