反射机制即允许程序获取自身的信息,并且可以操作类或者对象的方法或属性。在程序中一般的对象的类型在编译时就已经确定下来,然而通过反射则可以动态地创建对象且调用它的方法,程序事先并不知道对象的类型。
以Java为例,Java中的反射主要提供了这些功能:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法,甚至通过反射还可以调用private方法
- 在运行时调用任意一个对象的方法
光听概念其实还是有些抽象,那就通过动手来帮助理解吧
首先对于Java来说,反射主要分为三个步骤:
- 获取类的java.lang.class对象
- 获取目标类的各种信息(字段、方法、构造器、注解等)
- 生成对象
反射机制实例
首先定义一个Person类用于测试
public class Person {
private int age;
private String name;
//无参构造器
public Person(){
}
//构造器
public Person(String name,int age){
this.age=age;
this.name=name;
}
//getter
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void intro(){
System.out.println("Hello,My name is "+name+",and I'm "+age+" years old");
}
}
Step1 获取类的java.lang.Class对象
有三种方法获取
//method1 通过Class的静态方法forName类获取Class对象,需要catch处理异常
Class c =Class.forName("Person");
//method2 通过类的class属性
Class c=Person.class;
//method3 通过对象的getClass()方法
Class c=new Person().getClass();
Step2 获取类的信息、方法
获取类的属性字段
- getFields()——获取所有公有成员变量
- getField(String name)——获取指定名称的公有成员变量,会抛出NoSuchFieldException异常
- getDeclaredFields()——获取所有成员变量,不关乎权限
- getDeclaredFields(String name)——获取指定名称的成员变量,不关乎权限,会抛出NoSuchFieldException异常
Field[] fields=c.getDeclaredFields();
for(int i=0;i<fields.length;i++){
System.out.println(fields[i].getType().getName());
System.out.println(fields[i].getName());
}
获取类的方法
- getMethods()——获取所有公有方法
- getMethod(String methodName, Class<?> parameterTypes)——根据方法及形参列表获取公有方法,会抛出NoSuchMethodException异常
- getDeclaredMethods()——获取所有方法
- getDeclaredMethod(String methodName, Class<?> parameterTypes)——根据方法及形参列表获取方法,会抛出NoSuchMethodException异常
- getParameters()——获取所有形参
for(Method method: c.getMethods()){
System.out.println(method.getName());//输出方法名
System.out.println(method.getReturnType().getName());//输出返回值类型
System.out.println(method.getParameters());//获取形参信息
}
}
获取类的构造器
- getConstructors()——获取所有公有构造器
- getConstructor(Class<?> parameterTypes)——根据形参列表获取某一公有构造器
- getDeclaredConstructors()——获取所有构造器
- getDeclaredConstructor(Class<?> parameterTypes)——根据形参列表获取某一构造器
获取类的注解
- getAnnotations() ——获取所有注解
- getAnnotation(Class<?> annotationClass)——获取指定注释类型的注释
Step3 生成对象并调用其方法
Class[] pArgs=new Class[2];
pArgs[0]=String.class;
pArgs[1]=int.class;
Object obj=c.getDeclaredConstructor(pArgs).newInstance("rabbit",22);
Method method=c.getMethod("intro");
method.invoke(obj);
class.newInstance()
已被弃用,推荐使用class.getDeclaredConstructor().newInstance()
输出
Hello,My name is rabbit,and I'm 22 years old
反射与new的区别
了解了反射机制后不禁想,既然都是为了创建一个对象并使用,那为什么不简单地用new来创建对象呢?反射有什么好处?什么情况下需要用到反射?
首先分析一下new创建对象的过程:首次创建对象时,类加载器需要找到对应的class文件,之后类加载器再负责将class文件载入内存,然后生成class对象,期间把对象中的所有静态资源都执行以便,把这些静态资源存放到jvm的方法区中。使用new方法创建对象时,首先会检查该类class文件是否已经被加载到jvm内存并生成该类的class对象(class对象用来保存对象的类的信息),如果已经加载则在jvm堆内存中为该类分配足够的空间。接着清理所分配的存储空间,再把该类的所有基本数据类型设置成默认值,对象的引用设为null,然后再进行一些初始化操作,最后调用构造方法创建对象。
与new不同,反射是动态地创建对象,也就是说只有在程序运行的时候才会去获取类的实例。一般来说反射是配合配置文件来食用的,由于反射动态的特性,我们不需要将程序停下来,只需修改配置文件里面的相关信息而不需要去修改源码,IO流就会自己读取配置文件中的类名,通过class.forName()
方法找到对应的class文件并加载进内存,再调用newInstance()
方法来创建对象(此时创建的对象为Object类型,其类型需要转换成相对应的类类型)。这样一来将接口和实现解耦,就增加了程序的扩展性和灵活性。
当然反射机制也存在它的一些缺点:
- 反射属于一种解释操作,用于字段和方法接入时要远慢于直接代码,因此会带来性能下降的问题。
- 使用反射会模糊内部逻辑:反射由于绕过了源代码,我们源代码中看不到其中的逻辑,因而会带来维护的问题。