一文让你读懂“反射机制”!

Posted Putarmor

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文让你读懂“反射机制”!相关的知识,希望对你有一定的参考价值。

反射机制

1.定义

反射(reflection)机制:在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象都能够调用它的任意方法和属性,既然能拿到这些信息,我们就可以顺便修改部分类型信息。我们将这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制

2.用途

在日常开发过程中,经常会遇到某个类的成员变量、成员方法是私有属性,也就是说被private关键字进行修饰,此时针对这种情况,就可以用Java语言中的反射机制来获取需要的私有方法和私有属性。

反射最重要的用途就是开发一些通用框架,比如在spring中,我们将所有的Bean交给spring容器进行管理,无论是xml配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中结合的就是类的信息,spring根据这些信息,需要创建哪些Bean,spring就动态地创建这些类。

3.基本信息

Java程序中很多对象在运行时会有两种形态,分别是运行时类型编译时类型,比如一个向上转型的示例:Person p = new Student(),编译时期p的类型为Person,运行时期p的类型为Student,程序需要在运行时发现对象和类的真实信息,通过反射程序就能判断对象属于哪些类。

4.反射相关的类

反射中重要的类有四个:

类名用途
Class类代表类地实体,在运行地Java程序中表示类和接口
Field代表类地成员变量和类的属性
Method类代表类地方法
Constructor代表类的构造方法

反射机制起源

Java文件被编译后成生成了.class字节码文件,.class文件需要在JVM上去执行,JVM把**.class**文件解析为一个对象,这个对象就是java.lang.Class对象。

反射的根本:从Class对象出发
在这里插入图片描述

Person p1 = new Person();
Person p2 = new Person();
我们看到的是Person类具有多个实例,但是Class对象始终只有一个,因为Class对象是和.class文件对应的;当程序运行时,每个.java文件最终变成Class类对象的一个实例。

获得类相关的方法

方法用途
getClassLoader()获得类的加载器
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象(包含私有)
forName(String className)根据类名返回类的对象
newInstance()创建类的实例
getName()获得类的完整路径名字

获得类中属性相关的方法

方法用途
getField(String name)获得某个公有的属性对象
getFields()获得所有共有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象

获得类中构造器相关的方法

方法用途
getConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes)获得该类中与参数类型匹配的所有构造方法
getDeclaredConstructors()获得该类所有构造方法

获得类中方法相关的方法

方法用途
getMethod(String name, Class…<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有的公有方法
getDeclaredMethod(String name, Class…<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法

反射示例

获得Class对象的三种方式

在反射之前,需要做的第一步就是先拿到当前需要反射的类的Class对象,然后通过Class对象的核心方法,达到反射目的。也就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,那么我们就可以容易地修改一些信息!!!

第一种方式:使用Class.forName(“类的全路径名”);这是静态方法,使用该方法的前提是已经事先知道类的全路径名;
第二种方式:使用.class方法,仅仅适合在编译之前就已经明确要操作的类;
第三种方式:使用类对象的getClass()方法。

代码演示三种创建Class对象的方式:

class Student{
    //私有属性name
    private String name = "bit";
    //公有属性age
    public int age = 18;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }

    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("i am eat");
    }

    public void sleep(){
        System.out.println("i am pig");
    }

    private void function(String str) {
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '}';
    }
}

public class TestDemo {

    public static void main(String[] args) throws ClassNotFoundException {
        //1、用的最多的方式,但是可能抛出ClassNotFoundException异常
        Class<?> c1 = Class.forName("Student");
        
        //2、直接通过类名.class得到,该方法最安全可靠,程序性能更高,
        //说明每个类都有一个隐含的静态成员变量class
        Class<?> c2 = Student.class;

        //3、通过类对象调用getClass方法获得
        Student s = new Student();
        Class<?> c3 = s.getClass();

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);

    }
}

代码执行结果:

在这里插入图片描述
可以看出c1、c2、c3都是同一个实例对象,再一次印证了一个类在JVM中只有一个Class实例对象。

反射使用

通过反射创建对象

// 创建对象
    public static void reflectNewInstance() {
        try {
            //1、获得Class对象
            Class<?> cl = Class.forName("Student");
            //2、创建类的实例
            Student student = (Student) cl.newInstance(); //newInstance返回的是T类型
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

调用该方法执行的结果如下:

在这里插入图片描述
调用newInstance()时候调用的是Student的默认的无参构造方法,对于类的实例student来说,它的属性name值和age值是默认的成员属性值。

反射私有构造方法

	Class<?> cl = Class.forName("Student");
	//2、获取的是不带有参数的构造方法
	Constructor<?> constructor = cl.getConstructor();
	Student student = (Student) constructor.newInstance();
	System.out.println(student);

通过Class对象调用getConstructor()可以创建Class构造器,再使用构造器也可以创建类的实例对象,默认调用的是无参构造方法。

public static void reflectPrivateConstructor() {
        try {
            Class<?> cl = Class.forName("Student");
            //获得该类所有的构造方法,包含私有和公有
            //传参时,参数类型和原本带有参数的构造方法参数类型相匹配
            Constructor<?> constructor =
                 cl.getDeclaredConstructor(String.class, int.class);
            //反射私有东西时,要设置下面这句命令,否则将出现异常
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("trq", 24);
            System.out.println(student);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

程序执行结果:
在这里插入图片描述
当没有设置constructor.setAccessible(true)时,将引发下面的异常,可以看出这个时候我们就无法获得私有的构造方法。
在这里插入图片描述

反射私有属性

public static void reflectPrivateField() {
        try {
            Class<?> cl = Class.forName("Student");
            Field field = cl.getDeclaredField("name");
            Field field1 = cl.getDeclaredField("age");
            field.setAccessible(true);

            //拿到类的对象才能设置属性,因为成员属性是属于对象的
            Student student = (Student) cl.newInstance();
            System.out.println(student);
            field.set(student,"trq");
            field1.set(student,24);
            System.out.println(student);
            System.out.println(field.get(student)); //拿到对象的属性

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

程序执行结果如下:

在这里插入图片描述
可以看出,私有成员属性name被反射,对其值进行了修改。

反射私有方法

public static void reflectPrivateMethod() {
        try {
            //1、
            Class<?> cl = Class.forName("Student");
            //参数:要反射的方法的名称 与方法中参数匹配的类型
            Method method = cl.getDeclaredMethod("function",String.class);
            //下面的方法通过数组接收
            //Method[] methods =  cl.getDeclaredMethods();
            method.setAccessible(true);

            Student student = (Student) cl.newInstance();
            method.invoke(student,"我是trq!"); //

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

程序执行结果如下:

在这里插入图片描述
通过反射拿到了私有方法function(),并对该方法进行了修改

5.反射优点、缺点

反射优点

1.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;
2.增加程序的灵活性和扩展性,降低耦合性,提高自适应能力;
3.反射已经应用在很多流行框架如:spring Hibernate

反射缺点

1.使用反射存在效率问题,导致程序效率降低;

当去修改一个字段的值时,反射需要创建两个对象如下图所示;而不使用反射时我们直接可以通过set方法去设置成员属性的值,相比之下反射效率有所降低。
在这里插入图片描述

2.反射技术绕过了源代码的技术,因此会带来一定的维护问题,反射代码比相应的直接代码更加复杂。

希望你我他对反射有了更清晰的认识…

以上是关于一文让你读懂“反射机制”!的主要内容,如果未能解决你的问题,请参考以下文章

一文让你读懂分布式锁的使用原理及实现方式

一文让你读懂高并发编程的意义及其好处和注意事项

一文带你读懂Python中的进程

让你读懂synchronized的原理

一文带你读懂Dockerfile

一篇让你读懂java中的字符串(String)