一、反射
反射机制:将类的各个组成部分封装为其他对象,这就是反射机制。
反射的好处:
* 可以在程序运行过程中,操作这些对象。
* 可以解耦,提高程序的可扩展性。
Java代码在计算机中经历的三个阶段:
- Source源代码阶段:.java被编译成.class字节码文件。
- Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。
- RunTime运行时阶段:创建对象的过程new。
二、获取Class对象的三种方式
-
【Source源代码阶段】 Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
-
【Class类对象阶段】 类名.class:通过类名的属性class获取
多用于参数的传递。
-
【Runtime运行时阶段】对象.getClass():getClass()方法是定义在Objec类中的方法。
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
三、获取对象的方法测试
package com.cx;
import com.cx.entity.Person;
import org.junit.Test;
public class test {
@Test
public void test01() throws ClassNotFoundException {
// 1.Class.forName("全类名")
Class<?> clazz = Class.forName("com.cx.entity.Person");
System.out.println(clazz);
// 2.类名.class
Class<Person> clazz2 = Person.class;
System.out.println(clazz2);
// 3.对象.getClass()
Person person = new Person();
Class clazz3 = person.getClass();
System.out.println(clazz3);
// 比较三个对象
System.out.println(clazz==clazz2);
System.out.println(clazz2==clazz3);
}
}
结果:
四、Class对象功能
1. 获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
2. 获取构造方法们
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
3. 获取成员方法们:
* Method[] getMethods()
* Method getMethod(String name, 类<?>... parameterTypes)
* Method[] getDeclaredMethods()
* Method getDeclaredMethod(String name, 类<?>... parameterTypes)
4. 获取全类名
* String getName()
--------------------------------------------------------
* Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射
--------------------------------------------------------
* Constructor:构造方法
* 创建对象:
* T newInstance(Object... initargs)
* 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
--------------------------------------------------------
* Method:方法对象
* 执行方法:
* Object invoke(Object obj, Object... args)
* 获取方法名称:
* String getName:获取方法名
1. 测试getDeclaredFields()
@Test
public void test02() throws Exception {
Class<Person> person = Person.class;
// 获取所有public修饰的成员变量
Field[] fields = person.getFields();
for (Field field : fields) {
// 输出结果为空,因为都是private修饰的
System.out.println(field);
}
// 获取成员变量name的值
Field name = person.getDeclaredField("name");
//私有属性,暴力反射
name.setAccessible(true);
Person person1 = new Person();
Object o = name.get(person1);
System.out.println(o);
// 设置成员变量name的值
name.set(person1,"帅");
System.out.println(person1);
}
运行结果:
这里是获取获取所有的成员变量,不考虑修饰符getDeclaredFields(),name是private修饰的,所以要进行暴力反射,其他的几个同理。
- 测试获取构造方法
@Test
public void test03() throws Exception {
Class<Person> personClass = Person.class;
//获取有参构造器对象
Constructor<Person> constructor = personClass.getConstructor(String.class, int.class);
System.out.println(constructor);
//利用有参构造器创造对象
Person person = constructor.newInstance("帅", 22);
System.out.println(person);
System.out.println("-------------------");
//获取无参构造器
Constructor<Person> constructor1 = personClass.getConstructor();
//利用无参构造器创建对象
Person person1 = constructor1.newInstance();
System.out.println(person1);
//无参构造器创建对象可以用这个代替
Person person2 = personClass.newInstance();
System.out.println(person2);
}
结果:
无参构造器创建对象可以用操作可以简化:Class对象的newInstance方法。
- 获取成员方法们
package com.cx.entity;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println("eat.......");
}
public void eat(String food){
System.out.println("eat....."+food);
}
}
@Test
public void test04() throws Exception {
Class<Person> personClass = Person.class;
//获取指定名称的空参方法
Method eat_method1 = personClass.getMethod("eat");
//执行方法
Person person = new Person();
eat_method1.invoke(person);
System.out.println("---------------------");
//获取指定名称的有参方法
Method eat_method2 = personClass.getMethod("eat", String.class);
//执行方法
eat_method2.invoke(person,"苹果");
//获取方法名称
String name = eat_method2.getName();
System.out.println(name);
}
结果:
五、综合案例
需求:不改变该类代码的前提下,创建任意类的对象,执行任意的方法
- 创建pro.properties配置文件:
className=com.cx.entity.Person
methodName=eat
- 创建person和student实体
package com.cx.entity;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void eat(){
System.out.println("eat.......");
}
public void eat(String food){
System.out.println("eat....."+food);
}
}
package com.cx.entity;
public class Student {
public void sleep(){
System.out.println("sleep........");
}
}
- 代码执行
@Test
public void test05() throws Exception {
//1.加载配置文件
Properties properties = new Properties();
properties.load(test.class.getClassLoader().getResourceAsStream("pro.properties"));
//2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.加载该类进内存
Class<?> cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
- 结果
- 修改配置文件:
className=com.cx.entity.Student
methodName=sleep
- 修改后运行结果