什么是反射?
反射,是指在运行状态下,获取类中的属性和方法,以及调用其中方法的的一种机制。
这种机制的作用在于获取运行时才知道的类(Class)及其中的属性(Field)、方法(Method)以及调用其中的方法,也可以设置其中的属性值。
Java反射机制提供了一下几个功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的方法
利用反射可以 :
- 操作因访问控制权限限制的属性和方法
- 实现自定义注解
- 生成动态代理
- 根据需求加载类
反射的工作原理
Class 对象 :每一个Java类文件都会被编译成一个.class字节码文件,这些文件会在程序运行时被类加载器读入内存,将其放在运行时数据区的方法区内,并在堆中创建一个java.lang.Class 对象,用来封装方法区内的数据结构,这些Class对象包含了这个类的所有信息,包括父类,接口,构造方法,方法,属性等。
我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。
反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。
反射的用法
Person类声明如下:
class Person {
private String name;
private int age;
private long phoneNumber;
public Person() {
}
public Person(String name, int age, long phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
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;
}
public long getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(long phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", phoneNumber=" + phoneNumber +
'}';
}
}
实现反射的三种方式(获取Class对象)
Person person = new Person();
//①通过调用实例对象的getClass()方法
Class clazz = person.getClass();
//②通过类名调用class属性
Class clazz = Person.class;
//③通过Class.forName()方法,以全限定类名作为参数
Class clazz = Class.forName("cn.echo0.reflect.Person");//需要处理ClassNotFoundException
通过Class对象创建实例
具有无参构造函数时
Object obj = clazz.newInstance();//,需要处理IllegalAccessException, InstantiationException
构造器需要参数时
//如果需要参数则需要先获取到该类构造方法,并通过构造方法类获取实例:
Constructor constructor = clazz.getConstructor(String.class);//需要处理NoSuchMethodException
Object obj = constructor.newInstance("Echo0",20,15197401470l);//需要处理InvocationTargetException
获取属性
// 获取该类中的所有属性(包括私有属性)
Field[] fields = clazz.getDeclaredFields();
// 遍历输出所有属性
for(Field field : fields){
System.out.println("Modifier : \t" + Modifier.toString(field.getModifiers()));
System.out.println("Type : \t"+field.getType());
System.out.println("FieldName : \t"+field.getName());
}
修改私有属性的值
// 获取名为name的私有属性
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);//对于私有属性需要将其设置为可访问
Object name=field.get(object);
System.out.println(name);
// 修改属性
field.set(object,"Echo009");
获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
System.out.println("Modifier : \t" + Modifier.toString(method.getModifiers()));
System.out.println("MethodName : \t" + method.getName());
System.out.println("ReturnType : \t" + method.getReturnType());
// 获取参数类型
Class[] clazzs = method.getParameterTypes();
for (Class temp : clazzs) {
System.out.println("ParameterType \t: " + temp);
}
System.out.println("___________________________________________");
}
调用方法
// 获取名为getPhoneNumber的方法
Method method = clazz.getDeclaredMethod("getPhoneNumber");
Object phoneNumber = method.invoke(object);
System.out.println(phoneNumber);
总结
虽然反射非常的灵活,可以不受访问控制修饰符的限制,但同时也需要注意以下几个问题:
- 性能问题: 通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。
- 安全性问题: 反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;
- 兼容性问题: 因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;