反射机制即允许程序获取自身的信息,并且可以操作类或者对象的方法或属性。在程序中一般的对象的类型在编译时就已经确定下来,然而通过反射则可以动态地创建对象且调用它的方法,程序事先并不知道对象的类型。


Java为例,Java中的反射主要提供了这些功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法,甚至通过反射还可以调用private方法
  • 在运行时调用任意一个对象的方法

光听概念其实还是有些抽象,那就通过动手来帮助理解吧

首先对于Java来说,反射主要分为三个步骤:

  1. 获取类的java.lang.class对象
  2. 获取目标类的各种信息(字段、方法、构造器、注解等)
  3. 生成对象

反射机制实例

首先定义一个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);

Java9之后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类型,其类型需要转换成相对应的类类型)。这样一来将接口和实现解耦,就增加了程序的扩展性和灵活性。

当然反射机制也存在它的一些缺点:

  • 反射属于一种解释操作,用于字段和方法接入时要远慢于直接代码,因此会带来性能下降的问题。
  • 使用反射会模糊内部逻辑:反射由于绕过了源代码,我们源代码中看不到其中的逻辑,因而会带来维护的问题。
Last modification:September 4th, 2023 at 10:11 pm
If you think my article is useful to you, please feel free to appreciate