Java反射机制:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
反射的好处:
1.可以在程序运行过程中,操作这些对象。
2.可以进行解耦,提高程序的扩展性。
-
Java代码在计算机中的三个阶段
1. Sources源代码阶段:*.java被编译成*.class字节码文件。
2.Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于描述在内存中描述字节码文件),
Class对象将原字节码文件中的成员变量,构造函数,方法等的做了封装。
3.Runtime运行阶段:创建对象的过程new
-
讲解获取Class对象的方式
获取Class对象的三种方式对应着java代码在计算机中的三个阶段:
- 源代码阶段
Class.forName("全类名"):将字节码文件加载进内存,返回Class对象。
- Class类对象阶段
类名.class:通过类名的属性class获取
- Runtime运行时阶段
对象.getClass():getClass()方法是定义在Object类中的方法。
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
测试代码:
package com.company.reflect;
import com.company.reflect.domain.Person;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 10:37
* @Description:
*/
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:Class.forName("全类名")
Class<?> aClass = Class.forName("com.company.reflect.domain.Person");
System.out.println(aClass);
//方式二:类名.class
Class<Person> personClass = Person.class;
System.out.println(personClass);
//方式三:对象.getClass()
Person person = new Person();
Class<? extends Person> aClass1 = person.getClass();
System.out.println(aClass1);
//比较 == 三个对象
System.out.println(aClass == aClass1);
System.out.println(personClass==aClass1);
}
}
两个true表示Class对象是同一个。
-
获取Class对象功能
(1)获取成员变量们
Field[] getFields() :获取所有public修饰的成员变量
Field getField(String name) 获取指定名称的 public修饰的成员变量
Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
(2)获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
(3)获取成员方法们
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
-
Field:成员变量
先写一个测试类
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
public Person() {
}
public Person(String name, Integer 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 +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
//无参方法
public void eat(){
System.out.println("eat...");
}
//重载有参方法
public void eat(String food){
System.out.println("eat..."+food);
}
}
- 获取所有的public修饰的成员变量
//0.获取Person对象
Class<Person> personClass = Person.class;
//1.获取所有public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
- 获取特定的成员变量(public)
//2.Field getField(String name)
Field a = personClass.getField("a");
//获取成员变量a 的值 [也只能获取公有的,获取私有的或者不存在的字符会抛出异常]
Person person = new Person();
Object o = a.get(person);
System.out.println("o value: "+o);
//设置属性a的值
a.set(person,"haha");
System.out.println(person);
- 获取全部的成员变量
//Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField+" ");
}
System.out.println("==============================");
- 获取特定的成员变量,在这里如果需要对private进行修改,就必须进行暴力反射,将
d.setAccessible(true);设置为true
System.out.println("==============================");
Field d = personClass.getDeclaredField("d");
d.setAccessible(true);//暴力反射
d.get(person);
d.set(person,"222");
System.out.println(person);
-
普通方法获取
获取指定名称的方法(不带参数的获取)
Class<Person> personClass = Person.class;
//获取指定名称的方法
Method eat = personClass.getMethod("eat");
Person person = new Person();
eat.invoke(person);//执行方法
获取指定名称的方法(带参数获取)
//获取具有参数的构造方法
Method eat1 = personClass.getMethod("eat", String.class);
eat1.invoke(person,"fans");
System.out.println("===============================");
获取方法列表
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);//继承的方法也会被访问(前提是方法是public)
}
如果设置的方法中含有私有的方法,也可以设置d.setAccessible(true);设置为true,然后就可以访问私有方法。
-
构造方法
- 获取无参数的构造器
Class<Person> personClass = Person.class;
//Constructor<?>[] getConstructors()
Constructor<?>[] constructors = personClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
//Constructor<T> getConstructor(类<?>... parameterTypes)
//获取无参
Constructor<Person> constructor1 = personClass.getConstructor();
System.out.println(constructor1);
//利用获取的构造器创建对象
Person person = constructor1.newInstance();
System.out.println(person);
- 获取有参数的构造器
//获取有参
Constructor<Person> constructor = personClass.getConstructor(String.class,Integer.class);
System.out.println(constructor);
Person person1 = constructor.newInstance("PCB",100);
System.out.println(person1);
//理应Class类对象进行对象的构建获取
Person person2 = personClass.newInstance();
System.out.println(person2);
//对于getDeclaredConstructor方法和getDeclaredConstructors方法,此外在构造器的对象内也有setAccessible(true);方法,并设置成true就可以操作了。
-
设计简单的框架,理解反射的好处
准备测试类
package com.company.reflect.domain;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 13:27
* @Description:
*/
public class Student {
public void sleep(){
System.out.println("sleep...");
}
}
准备文件properties文件
className = com.company.reflect.domain.Student
methodName = sleep
- 需求
写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
- 实现
(1)配置文件 (2)反射
- 步骤
(1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中 (2)在程序中加载读取配置文件 (3)使用反射技术来加载类文件进内存 (4)创建对象 (5)执行方法
package com.company.reflect.反射案例;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* ⊙﹏⊙&&&&&&⊙▽⊙
*
* @Auther: pangchenbo
* @Date: 2020/5/9 13:30
* @Description:
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
/**
* 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
*/
//1.加载配置文件
//1.1创建Properties对象
Properties properties = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件 使用类加载器
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
properties.load(resourceAsStream);
//2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//加载类到内存中
Class<?> aClass = Class.forName(className);
//创建对象
Object o = aClass.newInstance();
//获取对象方法
Method method = aClass.getMethod(methodName);
//执行方法
method.invoke(o);
}
}
改变配置文件
className = com.company.reflect.domain.Person
methodName = eat
- 好处
我们这样做有什么好处呢,对于框架来说,是人家封装好的,我们拿来直接用就可以了,而不能去修改框架内的代码。但如果我们使用传统的new形式来实例化,那么当类名更改时我们就要修改Java代码,这是很繁琐的。修改Java代码以后我们还要进行测试,重新编译、发布等等一系列的操作。而如果我们仅仅只是修改配置文件,就来的简单的多,配置文件就是一个实实在在的物理文件。