Person.java经过javac编译,变为Person.class文件(字节码文件),字节码文件中,主要有类的属性、构造函数、方法,当然还有类的其他信息,这个阶段称为源码阶段,通过类加载器进入内存,在内存中生成一个Class对象,这个阶段为Class类对象阶段,一个类的Class对象中存储了类的全部信息,使用这个类对象的阶段称为Runtime运行时阶段。
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象.。反射就是把Java类中的各种成分映射成一个个的Java对象。
//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对象。
1)获取类的成员变量
//获取所有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 con = clazz1.getDeclaredConstructor(String.class, int.class);
System.out.println(con); //private ewen.Person(java.lang.String,int)
3)获取类中的方法
4)获取类的全路径名:String getName()
1)Field:成员变量对象
操作:
//获取指定的成员变量
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 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 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:构造方法对象
/获取构造函数
Constructor con1 = clazz1.getConstructor(String.class);
//创建对象
Object person3 = con1.newInstance("张三");
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法。
如果构造函数是private修饰的,也可以调用setAccessible(true)来忽略访问权限的安全检查。
Object person4 = clazz1.newInstance();
3)Method:方法对象
//获取方法对象
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() 可以获取真正的异常。
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
实现:
1. 配置文件
2. 反射
步骤:
1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2. 在程序中加载读取配置文件
3. 使用反射技术来加载类文件进内存
4. 创建对象
5. 执行方法
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文件就可以,在不改变代码的前提下,执行任意类的任意方法。
优点
正是反射有以上的特征,所以它能动态编译和创建对象,极大的激发了编程语言的灵活性,强化了多态的特性,进一步提升了面向对象编程的抽象能力,因而受到编程界的青睐
缺点
尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点。反射机制的功能非常强大,但不能滥用。在能不使用反射完成时,尽量不要使用,原因有以下几点:
Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。而且,如何使用反射决定了性能的高低。如果它作为程序中较少运行的部分,性能将不会成为一个问题。
使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
参考:
如有不足之处,欢迎指正,谢谢!