概述
反射:框架设计的灵魂
- 框架:半成品软件;可以在框架的基础上进行软件开发,简化编码。
- 反射:将类的各个组成部分封装为其他对象。
下面大概介绍一下Java代码在计算机中经历的三个阶段:
-
第一阶段——Source源代码阶段:
首先写一个类文件Person.java
,类里面可以写一些成员变量、构造方法、成员方法等;
经过javac
编译后在硬盘上生成一个字节码文件Person.class
,里面保存了成员变量、构造方法、成员方法等; -
第二阶段——Class类对象阶段
通过类加载器把字节码文件加载到内存里,内存中有一个Class 类对象(描述所有字节码文件的共同特征和行为),将成员变量封装为Field[ ]
对象,构造方法封装为Constructor[ ]
对象,成员方法封装为Method[ ]
对象 -
第三阶段——Runtime运行时阶段
new一个Person对象
反射的好处:
- 可以在程序运行过程中,操作这些对象;
- 可以降低程序的一些耦合性,提高程序的可扩展性。
获取Class对象
三种方式
Class.forName("全类名")
:将字节码文件加载进内存,返回Class对象;- 多用于配置文件,将类名定义在配置文件中;读取文件,加载类。
类名.class
:通过类名的属性class获取;- 多用于参数的传递。
对象.getClass()
:getclass()方法在Object类中定义;- 多用于对象的获取字节码的方式。
示例代码
在cn.blckder02.domain
下创建一个Person类,定义两个成员变量:
再创建一个student类,作对比,暂时不用写内容;
在cn.blckder02.reflect
下创建一个ReflectDemo1类,分别用以上三种方法获取Class对象,代码如下:
package cn.blckder02.reflect;
import cn.blckder02.domain.Person;
import cn.blckder02.domain.student;
public class ReflectDemo1 {
/**
获取Class对象的方式
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象;
2. 类名.class:通过类名的属性class获取;
3. 对象.getClass():getclass()方法在Object类中定义;
*/
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName("全类名")
Class cls1 = Class.forName("cn.blckder02.domain.Person");
System.out.println(cls1);
//2. 类名.class
Class cls2 = Person.class;
System.out.println(cls2);
//3. 对象.getClass()
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3);
//比较三个对象是否为同一个对象(比较的是内存地址)
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
//获取student
Class cls = student.class;
System.out.println(cls);
//比较cls与上一个对象是否为同一个对象
System.out.println(cls1 == cls);
}
}
程序运行结果如下,前两个比较都为true,说明同一个字节码文件(*.class)
在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个;
第三个比较为false,说明每个字节码文件对应的Class类对象都不相同。
使用Class对象
Class对象的获取功能
-
获取成员变量们
- Field[ ] getFields() 获取所有public修饰的成员变量
-
Field getField(String name) 获取指定名称public的成员变量
-
Field[ ] getDeclaredFields() 获取所有成员变量,不考虑修饰符
- Field getDeclaredField(String name) 获取所有指定名称的成员变量,不考虑修饰符
-
获取构造方法们
- Constructor<?>[ ] getConstructors() 获取所有public修饰的构造方法
-
Constructor< T> getConstructor(类<?>... parameterTypes) 获取指定名称public的构造方法
-
Constructor<?>[ ] getDeclaredConstructors() 获取所有构造方法,不考虑修饰符
- Constructo< T>r getDeclaredConstructor(类<?>... parameterTypes) 获取所有指定名称的构造方法,不考虑修饰符
-
获取成员方法们
- Method[ ] getMethods() 获取所有public修饰的成员方法
-
Method getMethod(String name, 类<?>... parameterTypes) 获取指定名称public的成员方法
-
Method[ ] getMethods() 获取所有成员方法,不考虑修饰符
- Method getDeclaredMethod(String name, 类<?>... parameterTypes) 获取所有指定名称的成员方法,不考虑修饰符
- 获取类名
- String getName()
示例
1. 获取成员变量们
在Person类中按如下属性添加a、b、c、d四个成员变量,并重新设置toString()
方法;
public String a;
protected String b;
String c;
private String d;
获取成员变量后可做操作:
- 设置值:
void set(Object obj, Object value)
- 获取值:
get(Object obj)
- 忽略访问权限修饰符的安全检查:
setAccessible(true)
(暴力反射)
在cn.blckder02.reflect
下创建ReflectDemo2类,代码如下:
package cn.blckder02.reflect;
import cn.blckder02.domain.Person;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
/*
1. 获取成员变量们
- Field[ ] getFields()
- Field getField(String name)
- Field[ ] getDeclaredFields()
- Field getDeclaredField(String name)
*/
//1.Field[ ] getFields()
Field[] fields = personClass.getFields();
for (Field field : fields){
System.out.println(field);
}
System.out.println("--------------------------------------------------");
//2. Field getField(String name)
//获取a成员变量
Field a = personClass.getField("a");
//获取成员变量a的值,a初始值为null
Person p = new Person();
Object value1 = a.get(p);
System.out.println(value1);
//设置a的值
a.set(p, "blckder02");
System.out.println(p);
System.out.println("--------------------------------------------------");
//3. Field[ ] getDeclaredFields()
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields){
System.out.println(declaredField);
}
System.out.println("--------------------------------------------------");
//4. Field getDeclaredField(String name)
//获取私有成员变量d
Field d = personClass.getDeclaredField("d");
//忽略访问权限修饰符的安全检查
d.setAccessible(true); //暴力反射
Object value2 = d.get(p);
System.out.println(value2);
}
}
由于getFields()
只能获取public修饰的成员变量,所以第一个只输出a;
而getDeclaredFields()
能获取所有成员变量,所以输出每个成员变量;
getDeclaredField(String name)
不能直接访问私有成员变量,但是可以使用setAccessible()
方法,忽略访问权限修饰符的安全检查来访问。
程序运行结果如下:
2. 获取构造方法们
getConstructors()
方法同样只能获取public修饰的构造方法。
构造方法的名称都与类名相同,唯一的区别就是参数不同,所以在获取带参构造方法时要传入相应的参数。
通过构造器来创建对象,需要用到newInstance()
方法;
如果使用空参构造方法创建对象,可以直接使用Class对象的newInstance()
方法,简化代码,获取到的结果是一样的。
同样可以通过暴力反射setAccessible()
方法获取私有构造方法。
在cn.blckder02.reflect
下创建ReflectDemo3类,代码如下:
package cn.blckder02.reflect;
import cn.blckder02.domain.Person;
import java.lang.reflect.Constructor;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
/*
2. 获取构造方法们
- Constructor<?>[ ] getConstructors()
- Constructor<T> getConstructor(类<?>... parameterTypes)
- Constructor<?>[ ] getDeclaredConstructors()
- Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
*/
//Constructor<?>[ ] getConstructors()
Constructor[] constructor0 = personClass.getConstructors();
for (Constructor constructor : constructor0){
System.out.println(constructor);
}
System.out.println("--------------------------------------------------");
//Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor constructor1 = personClass.getConstructor(String.class, int.class);
System.out.println(constructor1);
//用构造器创建对象
Object person1 = constructor1.newInstance("blckder02",20);
System.out.println(person1);
System.out.println("--------------------------------------------------");
//获取空参构造方法
Constructor constructor2 = personClass.getConstructor();
System.out.println(constructor2);
//用构造器创建对象
Object person2 = constructor2.newInstance();
System.out.println(person2);
System.out.println("--------------------------------------------------");
//简化
Object person3 = personClass.newInstance();
System.out.println(person3);
}
}
程序运行结果如下:
3. 获取成员方法们
在person类中添加一个空参成员方法eat()
,一个带参成员方法eat(String food)
:
public void eat(){
System.out.println("eat eat eat ...");
}
public void eat(String food){
System.out.println("I like eat "+food);
}
指定名称获取成员方法时,需要传入方法名以及参数。
执行方法时,需要用到invoke()
方法,要传入方法对象和实际的参数列表;
Object invoke(Object obj, Object...args)
获取所有public修饰的成员方法时,不仅会得到自己设置的几个方法,还有Object类、父类等的方法。
调用方法的getName()
可以获取到方法名称。
同样可以通过暴力反射setAccessible()
方法获取私有成员方法。
在在cn.blckder02.reflect
下创建ReflectDemo4类,代码如下:
package cn.blckder02.reflect;
import cn.blckder02.domain.Person;
import java.lang.reflect.Method;
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class personClass = Person.class;
/*
3. 获取成员方法们
- Method[ ] getMethods()
- Method getMethod(String name, 类<?>... parameterTypes)
- Method[ ] getMethods()
- Method getDeclaredMethod(String name, 类<?>... parameterTypes)
*/
//Method getMethod(String name, 类<?>... parameterTypes)
//获取空参方法
Method eat_method1 = personClass.getMethod("eat");
Person p = new Person();
//执行方法
eat_method1.invoke(p);
System.out.println("--------------------------------------------------");
//获取带参方法
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(p, "Apple");
System.out.println("--------------------------------------------------");
//Method[ ] getMethods()
Method[] methods = personClass.getMethods();
for (Method method :methods){
String name = method.getName();
System.out.println(name+" / "+method);
}
}
}
程序运行结果如下:
4. 获取类名
调用类的getName()
方法,获取到的是全类名;
案例
目的: 写一个类似“框架”的程序,在不改变程序任何代码的情况下,我们可以创建任意类对象,并执行其中方法。
可以通过写配置文件
和反射技术
来实现;
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载文件进内存
- 创建对象
- 执行方法
示例代码:
配置文件config
,类名参数值要写全类名:
className = cn.blckder02.domain.Person
methodName = eat
测试程序ReflectTest.java
:
package cn.blckder02.reflect;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectTest {
public static void main(String[] args) throws Exception {
//1.加载配置文件
Properties pro = new Properties(); //创建Properties对象
//获取class目录下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader(); //获取字节码文件对应的类加载器
InputStream bs = classLoader.getResourceAsStream("config"); //获取对应配置文件的字节流
pro.load(bs); //加载字节流
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//执行方法
method.invoke(obj);
}
}
程序运行结果:
在不改动ReflectTest.java
代码的情况下,修改配置文件,来调用student类中的sleep方法;
此时程序运行结果:
这就是框架的基本形式,只用修改少量地方,就能实现多种方法;
而反射就是去获取我们看不见的构造器、对象、方法等。
有什么写得不对的地方还请各位师傅指教。
BY:先知论坛
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论