自学内容网 自学内容网

Java反射机制

一、反射的概述

Java反射(Reflection)机制是指在程序运行状态中,可以动态地访问和操作类的属性和方法。通过反射,程序可以在运行时获取类的信息,包括类的构造函数、方法、字段等的所有信息,并可以通过这些信息来创建对象、调用方法以及访问和修改字段的值。

二、反射的原理

Java反射机制基于Java的运行时数据区域(Runtime Data Area)和类加载机制。当Java虚拟机(JVM)加载一个类时,它会将类的字节码文件加载到内存中,并在方法区创建一个Class对象来表示该类。这个Class对象包含了类的完整信息,包括类的构造函数、方法、字段等。通过反射API,我们可以获取到这个Class对象,进而获取到类的各种信息并进行操作。

三、获取class对象的三种方式

在Java中,获取Class对象的方式主要有三种,这些方法允许开发者在运行时动态地获取和操作类的信息。以下是这三种方式的详细解释:

1. 使用Object.getClass()方法

  • 描述:当已经有一个类的实例时,可以通过调用该实例的getClass()方法来获取其Class对象。这是最直接且常用的方式之一。
  • 示例代码
    String str = "hello";  
    Class<?> clazz = str.getClass();  
    System.out.println(clazz.getName()); // 输出 java.lang.String

2. 使用类的.class属性

  • 描述:对于任何类,都可以通过在其类名后添加.class来获取该类的Class对象。这种方式不依赖于类的实例,即使没有类的实例也可以获取Class对象。
  • 示例代码
    Class<?> clazz = String.class;  
    System.out.println(clazz.getName()); // 输出 java.lang.String


    注意,这种方式不仅可以用于引用类型,还可以用于基本数据类型(通过其包装类的.TYPE属性)和数组。

3. 使用Class.forName(String className)方法

  • 描述Class.forName(String className)方法通过类的全限定名(即包括包名的类名)来动态加载该类,并返回对应的Class对象。如果该类尚未被加载到JVM中,此方法会触发类的加载。
  • 示例代码
    try {  
        Class<?> clazz = Class.forName("java.lang.String");  
        System.out.println(clazz.getName()); // 输出 java.lang.String  
    } catch (ClassNotFoundException e) {  
        e.printStackTrace();  
    }


    注意:使用此方法时,需要处理ClassNotFoundException异常,因为如果JVM找不到指定的类,就会抛出此异常。

额外说明

  • 安全性:使用Class.forName()方法时需要注意安全性,因为它会动态加载类,这可能会被恶意代码利用。
  • 效率:与.class方式相比,Class.forName()方法需要更多的时间来查找和加载类,因此在性能敏感的场景下应谨慎使用。
  • 用途.class方式通常用于编译时已经知道的类,而Class.forName()方法则更多地用于运行时动态加载类,例如,在实现依赖注入或加载插件时。

总的来说,Java提供了灵活的方式来在运行时获取和操作类的信息,这使得Java成为一种动态性很强的编程语言。

四、利用反射获取构造方法

获取了Class对象后,可以使用以下方法来获取构造方法:

  1. getConstructors():返回类中所有公共构造方法的数组。它不会返回受保护、默认(包)访问或私有的构造方法。

  2. getDeclaredConstructors():返回类中声明的所有构造方法,包括公共、受保护、默认(包)访问和私有的构造方法。

  3. getConstructor(Class<?>... parameterTypes):返回与参数类型匹配的公共构造方法。如果找不到匹配的构造方法,将抛出NoSuchMethodException

  4. getDeclaredConstructor(Class<?>... parameterTypes):返回与参数类型匹配的构造方法,不考虑访问修饰符。如果找不到匹配的构造方法,将抛出NoSuchMethodException

示例代码

假设有一个Student类,它有几个不同访问修饰符的构造方法:

package edu.etime.reflex.demo2;

public class Student {
    private String name;
    private int age;

    public Student() {
        // 无参构造
    }

    public Student(String name) {
        this.name = name;
    }

    protected Student(int age) {
        this.age = age;
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略getter和setter方法


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

以下是如何使用反射来获取这些构造方法的示例:

package edu.etime.reflex.demo2;

import java.lang.reflect.Constructor;

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        // 获取Class对象   edu.etime.reflex.demo2.Student是Student类的全限定类名
        Class<?> clazz = Class.forName("edu.etime.reflex.demo2.Student");
        // 获取所有构造方法(包括私有)
        System.out.println("==所有构造方法(包括私有)==");
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<?> con : constructors) {
            System.out.println(con);
        }
        System.out.println("===========================");
        // 获取特定的构造方法
        // 公共的无参构造方法
        System.out.println("===公共的无参构造方法===");
        Constructor<?> publicConstructor = clazz.getConstructor();
        System.out.println(publicConstructor);
        System.out.println("===========================");

        System.out.println("====公共的带有String参数的构造方法===");
        // 公共的带有String参数的构造方法
        Constructor<?> publicConstructorWithString = clazz.getConstructor(String.class);
        System.out.println(publicConstructorWithString);
        System.out.println("===========================");


        // 私有的带有String和int参数的构造方法
        System.out.println("===私有的带有String和int参数的构造方法===");
        Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class, int.class);

        System.out.println(privateConstructor);
        System.out.println("===========================");

        System.out.println("===使用构造方法创建对象(以私有构造方法为例)===");
        // 使用构造方法创建对象(以私有构造方法为例)
        // 注意:如果构造方法是私有的,需要设置setAccessible(true)来绕过Java的访问控制检查 否则使用私有构造方法创建对象会报错
        privateConstructor.setAccessible(true);
        Object student = privateConstructor.newInstance("Alice", 20);
        System.out.println(student); // 这里可能需要重写toString()方法来显示更有用的信息



    }
}

运行效果:

          privateConstructor.setAccessible(true);
        Object student = privateConstructor.newInstance("Alice", 20);
        System.out.println(student); // 这里可能需要重写toString()方法来显示更有用的信息

上面使用私有的构造方法,创建对象的这种方式也叫做暴力反射。

通过反射获取方法的修饰符示例:

package edu.etime.reflex.demo2;

import java.lang.reflect.Constructor;

/**
 * @Date 2024/7/20 17:17
 * @Author liukang
 **/
public class ReflectDemo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = Class.forName("edu.etime.reflex.demo2.Student");
        Constructor<?>[] constructors = clazz.getConstructors();
        if(constructors.length>0){
            Constructor<?> constructor = constructors[0];
            // 获取该方法的权限修饰符
            int modifiers = constructor.getModifiers();
            // 获取方法名
            String name = constructor.getName();
            System.out.println(name+"的权限修饰符是"+modifiers);
        }
    }
}

运行效果:

查看java的中文文档中有关常量字段值的内容

可知1对应的修饰符是public

使用场景:

比如idea中使用new 创建一个对象时,idea会提示有哪些构造函数。

如下图,使用私有的构造方法通过new的方式进行创建对象,也会有提示,其实都是利用了反射的原理。

 

五、反射获取成员变量

在Java中,利用反射获取类的成员变量(也称为字段或属性)主要涉及到java.lang.reflect.Field类。以下是几种通过反射获取成员变量的方法:

1. getField(String name)

  • 用途:此方法用于获取类中声明为public的字段。
  • 参数:字段的名称(String类型)。
  • 返回值:一个Field对象,表示指定的公共字段;如果找不到该字段,则抛出NoSuchFieldException
  • 注意:它不能获取非公共字段(即受保护的、默认的或私有的字段)。

2. getDeclaredField(String name)

  • 用途:此方法用于获取类中声明的任何访问权限的字段,包括公共、受保护、默认(包)访问和私有字段。
  • 参数:字段的名称(String类型)。
  • 返回值:一个Field对象,表示指定的字段;如果找不到该字段,则抛出NoSuchFieldException
  • 注意:它可以获取所有访问权限的字段,但如果需要访问私有字段,则可能需要调用setAccessible(true)来绕过Java的访问控制检查。

3. getFields()

  • 用途:此方法返回一个包含类中所有公共字段的Field数组。
  • 参数:无参数。
  • 返回值:一个Field数组,包含类中声明的所有公共字段。
  • 注意:它不包含非公共字段。

4. getDeclaredFields()

  • 用途:此方法返回一个包含类中声明的所有字段(不考虑访问权限)的Field数组。
  • 参数:无参数。
  • 返回值:一个Field数组,包含类中声明的所有字段,包括私有字段。
  • 注意:尽管返回的字段包括私有字段,但访问私有字段时可能需要调用setAccessible(true)

示例代码:

假设我们有以下Person类:

public class Person {  
    private String name;  
    protected int age;  
    public String email;  
  
    // 省略构造方法、getter和setter  
}

以下是如何使用反射获取这些字段的示例:

import java.lang.reflect.Field;  
  
public class ReflectDemo {  
    public static void main(String[] args) throws Exception {  
        Person person = new Person();  
  
        // 获取Class对象  
        Class<?> clazz = person.getClass();  
  
        // 获取公共字段  
        Field emailField = clazz.getField("email");  
        System.out.println("Public field: " + emailField.getName());  
  
        // 获取所有字段(包括私有)  
        Field[] fields = clazz.getDeclaredFields();  
        for (Field field : fields) {  
            System.out.println("Declared field: " + field.getName());  
  
            // 如果需要访问私有字段,可以取消注释以下行  
            // field.setAccessible(true);  
        }  
  
        // 特定获取私有字段  
        Field nameField = clazz.getDeclaredField("name");  
        // 访问私有字段时,可能需要设置可访问性  
        nameField.setAccessible(true);  
        // 注意:这里不能直接通过nameField.get(person)获取值,因为name是私有的且未初始化  
        // 如果需要获取或设置私有字段的值,请确保对象已正确初始化,并使用set/get方法(如果有的话)或通过反射直接操作  
    }  
}

运行效果:

 六、反射获取成员方法

在Java中,利用反射获取类的成员方法(也称为函数或操作)主要涉及到java.lang.reflect.Method类。以下是几种通过反射获取成员方法的方法:

1. getMethod(String name, Class<?>... parameterTypes)

  • 用途:此方法用于获取类中声明为public的且与指定名称和参数类型列表匹配的方法。
  • 参数
    • name:方法的名称(String类型)。
    • parameterTypes:方法的参数类型列表(Class类型数组),如果方法没有参数,则传递一个长度为0的数组。
  • 返回值:一个Method对象,表示指定的公共方法;如果找不到该方法,则抛出NoSuchMethodException
  • 注意:它不能获取非公共方法(即受保护的、默认的或私有的方法)。

2. getDeclaredMethod(String name, Class<?>... parameterTypes)

  • 用途:此方法用于获取类中声明的任何访问权限的且与指定名称和参数类型列表匹配的方法,包括公共、受保护、默认(包)访问和私有方法。
  • 参数:与getMethod相同。
  • 返回值:一个Method对象,表示指定的方法;如果找不到该方法,则抛出NoSuchMethodException
  • 注意:它可以获取所有访问权限的方法,但如果需要访问私有方法,则可能需要调用setAccessible(true)来绕过Java的访问控制检查。

3. getMethods()

  • 用途:此方法返回一个包含类中所有公共方法的Method数组,包括继承自父类的方法(但不包括继承自java.lang.Object的方法,除非它们被该类或父类覆盖)。
  • 参数:无参数。
  • 返回值:一个Method数组,包含类中声明的所有公共方法。
  • 注意:它不包含非公共方法。

4. getDeclaredMethods()

  • 用途:此方法返回一个包含类中声明的所有方法(不考虑访问权限)的Method数组,但不包括继承的方法。
  • 参数:无参数。
  • 返回值:一个Method数组,包含类中声明的所有方法,包括私有方法。
  • 注意:尽管返回的方法包括私有方法,但访问私有方法时可能需要调用setAccessible(true)

示例代码:

假设我们有以下Person类:

public class Person {  
    private void privateMethod() {  
        // 私有方法实现  
    }  
  
    protected void protectedMethod() {  
        // 受保护方法实现  
    }  
  
    public void publicMethod() {  
        // 公共方法实现  
    }  
  
    // 省略其他成员  
}

以下是如何使用反射获取这些方法的示例: 

 

import java.lang.reflect.Method;  
  
public class ReflectDemo {  
    public static void main(String[] args) throws Exception {  
        Person person = new Person();  
  
        // 获取Class对象  
        Class<?> clazz = person.getClass();  
  
        // 获取公共方法  
        Method publicMethod = clazz.getMethod("publicMethod");  
        System.out.println("Public method: " + publicMethod.getName());  
  
        // 获取所有声明的方法(包括私有)  
        Method[] declaredMethods = clazz.getDeclaredMethods();  
        for (Method method : declaredMethods) {  
            System.out.println("Declared method: " + method.getName());  
  
            // 如果需要访问私有方法,可以取消注释以下行  
            // method.setAccessible(true);  
        }  
  
        // 特定获取私有方法  
        Method privateMethod = clazz.getDeclaredMethod("privateMethod");  
        // 访问私有方法时,需要设置可访问性  
        privateMethod.setAccessible(true);  
        // 注意:私有方法不能直接通过反射调用,除非它是静态的或者你已经有了对象的实例  
        // 这里只是展示了如何获取私有方法的Method对象  
    }  
}

运行效果:


原文地址:https://blog.csdn.net/qq_44286009/article/details/140572288

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!