作者 | ewenll
责编 | 夕颜
出品 | CSDN博客
Java代码在计算机中经历的阶段:三个阶段 Person.java经过javac编译,变为Person.class文件(字节码文件),字节码文件中,主要有类的属性、构造函数、方法,当然还有类的其他信息,这个阶段称为源码阶段,通过类加载器进入内存,在内存中生成一个Class对象,这个阶段为Class类对象阶段,一个类的Class对象中存储了类的全部信息,使用这个类对象的阶段称为Runtime运行时阶段 Java识别类和对象信息的两种方式- 一种是传统的RTTI(Run-Time Type Identification),它假定我们在编译时已经知道了所有的类型信息;
- 另一种是反射机制,它允许我们在运行时发现和使用类的信息。使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)
- 类名.class:该方法最为安全可靠,程序性能更高。这说明任何一个类都有一个隐含的静态成员变量 class。这种方法只适合在编译前就知道操作的 Class,多用于传递参数。
- Class.forName(“类的全路径名”) :当知道某类的全路径名时,可以使用此方法获取 Class 类对象。用的最多,但可能抛出 ClassNotFoundException 异常,多用于加载配置文件。
- 对象名.getClass():getClass()是Object中的方法,java中所有类都直接或间接继承了Object类,所以都有getClass()方法。
//ewen包下的Person类
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
//构造函数、get、set方法省略。
public void sleep(){
System.out.println("sleep....");
}
}
public class ReflectTest {
public static void main(String[] args) throws Exception {
//获取Person类的Class对象
// 通过类名.class获取
Class clazz1 = Person.class;
//通过Class.forName("类的全路径名")
Class clazz2 = Class.forName("ewen.Person");
//通过对象名.getClass()方法获得
Person person = new Person();
Class clazz3 = person.getClass();
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz2 == clazz3); //true
}
}
一个类只有一个Class实例,无论通过哪种方式获取,得到的都是同一个Class对象。
Class对象的功能
1)获取类的成员变量
- Field[] getFields() :获取所有public修饰的成员变量,返回一个含有所有public修饰的成员变量对象的数组;
- Field getField(String name):获取指定名称的 public修饰的成员变量,返回一个成员变量对象。
- Field[] getDeclaredFields() :获取所有的成员变量,不考虑修饰符。
- Field getDeclaredField(String name) :获取指定名称的成员变量对象,不考虑修饰符。
//获取所有pubic修饰的成员变量
Field[] fields = clazz1.getFields();
for (Field field : fields) {
System.out.println(field); //public java.lang.String ewen.Person.a,只有a是public修饰的
}
//获取指定的成员变量
Field field = clazz1.getField("a");
System.out.println("成员变量a:" + field); /public java.lang.String ewen.Person.a
//使用getField()方法获取非public修饰的方法,报NoSuchFieldException
/* Field field2 = clazz1.getField("b");
System.out.println("protected修饰的b" + field2);*/
//获取所有成员变量
Field[] fields2 = clazz1.getDeclaredFields();
for (Field field1 : fields2) {
System.out.println(field1); //所有成员变量
}
//获取指定成员变量,不考虑修饰符
Field field3 = clazz1.getDeclaredField("d");
System.out.println(field3); /public java.lang.String ewen.Person.d
2)获取类的构造方法
- Constructor>[] getConstructors() :获取所有public 修饰的构造方法,返回一个含有所有public修饰的构造函数对象的数组。
- Constructor getConstructor(类>… parameterTypes) :获取含有指定参数public修饰的构造函数。
- Constructor>[] getDeclaredConstructors() :获取所有构造函数,不考虑修饰符。
- Constructor getDeclaredConstructor(类>… parameterTypes) :获取所有构造函数,不考虑修饰符,参数是构造器中参数类型对应的Class对象。
//获取构造函数
Constructor con = clazz1.getDeclaredConstructor(String.class, int.class);
System.out.println(con); //private ewen.Person(java.lang.String,int)
3)获取类中的方法
- Method[] getMethods() :获取所有public修饰的方法,返回一个含有所有public修饰的方法对象的数组。
- Method getMethod(String name, 类>… parameterTypes) :获取public修饰的含有指定参数的方法。
- Method[] getDeclaredMethods() :获取所有方法,不考虑修饰符
- Method getDeclaredMethod(String name, 类>… parameterTypes) :获取指定方法,不考虑修饰符。
- 设置成员变量值:field.set(类对象名,value)
//获取指定的成员变量
Field field = clazz1.getField("a");
System.out.println("成员变量a:" + field);
Person person2 = new Person();
//设置值
field.set(person2,"aaa");
System.out.println("a的值:" + person2.getA()); //a的值:aaa
- 获取成员变量值:field.get(类对象名)
//获取指定的成员变量
Field field = clazz1.getField("a");
System.out.println("成员变量a:" + field);
Person person2 = new Person();
//获取person2中的a的值
Object value = field.get(person2);
System.out.println("a的值:" + value); //a的值为null,String类型的成员变量默认为null。
- 忽略访问权限修饰符的安全检查:field.setAccessible(true)
//获取指定成员变量,不考虑修饰符
Field field3 = clazz1.getDeclaredField("d");
System.out.println(field3);
//设置值
Person person1 = new Person();
/*
忽略访问权限修饰符的安全检查,d是private修饰的,如果不忽略
访问权限修饰符的安全检查,会报IllegalAccessException异常。
*/
field3.setAccessible(true);
field3.set(person1,"111");
System.out.println("d的值:" + person1.getD()); //d的值:111
2)Constructor:构造方法对象
- 创建对象:T newInstance(Object… initargs) ,里面为可变参数,变量的值。
/获取构造函数
Constructor con1 = clazz1.getConstructor(String.class);
//创建对象
Object person3 = con1.newInstance("张三");
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。
如果构造函数是private修饰的,也可以调用setAccessible(true)来忽略访问权限的安全检查。
Object person4 = clazz1.newInstance();
3)Method:方法对象
- 执行方法:Object invoke(类对象名, Object… args) ,参数必须得有类对象,变量参数根据方法参数来定
//获取方法对象
Method method = clazz1.getMethod("sleep");
//执行方法
method.invoke(person1);
Method 调用 invoke() 的时候,存在许多细节:
invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 obj 为 null,后面的可变参数 Object 对应的自然就是参数。
invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。
- 获取方法名称 String getName()
pro.properties文件:
pro.className=ewen.Person
pro.methodName=sleep
public class UseReflect {
public static void main(String[] args) throws Exception {
//加载配置文件
/*
创建Properties对象。
Properties是Java中jdk自带的一个对象
我们可以直接将后缀为properties的文件变为Properties对象,
然后通过Porperties对象中的方法对.properties文件对象进行操作
*/
Properties pro = new Properties();
//获得配置文件(pro.properties)的字节流
InputStream is = UseReflect.class.getClassLoader().getResourceAsStream("pro.properties");
//从输入流中读取属性列表(键和元素对)。
pro.load(is);
//获取配置文件中定义的数据
String className = pro.getProperty("pro.className");
String methodName = pro.getProperty("pro.methodName");
//获取Class对象
Class clazz = Class.forName(className);
//获取类对象
Object obj = clazz.newInstance();
//获取方法对象
Method method = clazz.getMethod("sleep");
//执行方法
method.invoke(obj);
}
}
只需更改配置文件pro.properties文件就可以,在不改变代码的前提下,执行任意类的任意方法。
反射的优缺点
优点
- 反射机制极大的提高了程序的灵活性和扩展性,降低模块的耦合性,提高自身的适应能力。
- 通过反射机制可以让程序创建和控制任何类的对象,无需提前硬编码目标类。
- 使用反射机制能够在运行时构造一个类的对象、判断一个类所具有的成员变量和方法、调用一个对象的方法。
【END】
更多精彩推荐
☞突发!印度封禁抖音、微信、快手等 59 款中国 App
☞深度学习先驱 Yann LeCun 被骂到封推!AI 偏见真该甩锅数据集?
☞他被称为"中国第一程序员",一人之力单挑微软,如今拜入武当修道
☞高文、张钹、杨强隔空论道:AI精度与隐私的博弈
☞带你从零入门 Serverless | 一文详解 Serverless 架构模式
☞中央银行数字货币的总体框架
欢迎“分享”、“点赞”、“在看”三连走起!