【Java 基础语法】详解 Java 的反射

x33g5p2x  于2021-12-18 转载在 Java  
字(5.9k)|赞(0)|评价(0)|浏览(405)

1. 定义

Java 的反射(Reflection)机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。因此,通过它我们就能够修改部分类型信息,而这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制

Java 的反射机制可以形象的理解为安检的机器或者是照妖镜,因为它和安检机器和照妖镜一样都能够显露出事物内部的属性

注意:
Java 的反射是在运行期间获取类型的信息,是在运行时的一种机制

2. 为什么要有反射

疑问: Java 已经有了封装为什么还要有反射呢?这貌似不就破坏了封装的特性吗?

解答: 这里截取了 Oracle 官方文档的一些话来回答这个问题

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率

毕竟,没有最好,只有更好!反射机制就可以让我们不断地变得更好

2. 用途

这里介绍几点关于反射的用途,例如:

  • 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法是私有的或是只对系统应用开发,这时就可以利用 Java 的反射机制,通过反射来获取所需的私有成员或方法
  • 反射最重要的用途就是开发各种通用框架,比如在 Spring 中,我们会将所有的类 Bean 交给 Spring 容器管理。无论是 XML 配置 Bean,还注解配置,当我们从容器中获取 Bean 来依赖注入时,容器会读取配置,而配置中给的就是类的信息,Spring 会根据这些信息,需要创建哪些 Bean 就动态的创建这些类
  • Java 程序中有许多对象在运行时会出现两种类型:运行时类型和编译时类型,例如
Person p=new Student();

这句代码中,p 在编译时类型为 Person,但在运行时类型为 Student。程序需要在运行时发现对象和类的真实信息,而通过使用反射,程序就能够正确判断出该对象和类是属于哪些类

3. 反射相关的类

3.1 总览图

Java 类的成员包括以下三类:成员变量、成员方法、构造方法。反射相关的类就是和这几个成员相关

下面将介绍一些关于这几类相关的方法,但是并不全。想查询全部相关方法,可以去帮助文档了解

3.2 Class 类相关的方法

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

3.3 Filed 类相关的方法

方法说明
getFiled(String name)获得某个公有的属性对象
getFileds()获得所有公有的属性对象
getDeclaredFiled(String name)获得某个属性对象
getDeclaredFileds()获得所有属性对象

3.4 Method 类相关的方法

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

3.5 Constructor 类相关的方法

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

4. 获取 Class 对象

4.1 Class 类(反射机制的起源)

Class 类代表类的实体,在运行的 Java 应用程序中表示类和接口

Java 文件被编译后,会生成 .class 字节码文件,而字节码文件最终又被加载到了 JVM 中(具体加载到了 JVM 的方法区),之后 JVM 就会将要使用的字节码文件解析为一个对象,而这个对象就是 java.lang.Class,它存储在 JVM 的堆中。

因此,当程序运行时,每个 Java 文件最终就变成了 Class 类对象的一个实例。我们通过 Java 的反射机制应用到这个实例,就可以去获得甚至去添加、改变这个类的属性和行为,使得这个类成为一个动态的类

注意:

  • Class 是一个实实在在的类,位于 java.lang 包中
  • 每个 Java 类运行时都在 JVM 里表现为一个 Class 对象
  • 数组也同样会被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象
  • 基本类型和 void 同样也会被表现为 Class 对象

以下均为对下面的 Student 类来获取 Class 对象进行示例(可以先跳过这段代码)

package demo;

public class Student{ 
    // 私有属性 name 
    private String name = "lb"; 
    // 公有属性 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("eat"); 
    }
    public void sleep(){ 
        System.out.println("sleep"); 
    }
    private void function(String str) { 
        System.out.println(str); 
    }
    @Override 
    public String toString() { 
        return "Student{" + "name='" + name + '\'' +
                ", age=" + age + 
                '}'; 
    } 
}

4.2 获取 Class 对象三种方式

在反射之前,我们需要做的第一步就是先拿到当前需要反射的类的 Class 对象,然后通过 Class 对象的核心方法,达到反射的目的。

  • 第一种(使用最多,前提是已明确类的全路径): 使用 Class.forName("类的全路径名"); 静态方法获取
try{
	Class c1=Class.forName("demo.Student");
}catch(ClassNotFoundException e){
    e.printStackTrace();
}

要处理异常是因为,Class 的 forName 方法中抛出了 ClassNotFoundException 异常,防止输入的字符串不能找到对应的类

  • 第二种(仅适合在编译前就已经明确要操作的 Class): 使用类的 class 属性获取
Class c2=Student.class;

该方式说明了任何一个类都有一个隐藏的静态成员变量 class

  • 第三种(不通用,需要构造类的对象): 使用对象的 getClass() 方法获取
Student student=new Student();
Class c3=student.getClass();

注意:
一个类对应一个 Class 对象,可以通过对上述三个获取的 Class 对象进行引用值的比较来证明

5. 反射的使用

接下来将以上述的 Student 类为例,来对它进行反射的操作示例

注意:
所有和反射相关的包都在 java.lang.reflect 包下,使用前需要导包

补充:

将此对象的 accessible 标志设置为指示的布尔值

  • 值为 true,则指示反射的对象在使用时应该取消 Java 语言访问检查。
  • 值为 false,则指示反射的对象应该实施 Java 语言访问检查、

实际上 setAccessible 是启用和禁用访问安全检查的开关,并不是为 true 就能访问,为 false 就不能访问。由于 JDK 的安全检查耗时较多,所以通过 setAccessible(true) 的方式关闭安全检查就可以达到提升反射速度的目的

示例:

public class ReflectClassDemo {
    // 创建对象
    public static void reflectNewInstance(){
        Class<?> c=null;
        try {
            // 获取 Class 对象
            c=Class.forName("demo.Student");
            // 通过获取的对象来创建 Student 的对象
            Student student=(Student) c.newInstance();
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 反射私有构造方法
    public static void reflectPrivateConstructor(){
        Class<?> c=null;
        try {
            // 获取 Class 的对象
            c=Class.forName("demo.Student");
            // 获取 Student 中私有的构造方法
            Constructor<?> constructor=c.getDeclaredConstructor(String.class,int.class);
            // 设置为 true 后可以修改访问权限
            constructor.setAccessible(true);
            // 通过获取的私有构造方法来创建 Student 对象
            Student student=(Student) constructor.newInstance("张三",18);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 反射私有属性
    public static void reflectPrivateFiled(){
        Class c=null;
        try {
            // 获取对象
            c=Class.forName("demo.Student");
            // 获取私有属性
            Field filed=(Field) c.getDeclaredField("name");
            filed.setAccessible(true);
            // 通过获取的对象实例化
            Student student=(Student) c.newInstance();
            // 修改 student 这个对象的 filed 字段,将其值修改为 张三
            filed.set(student,"张三");
            String name=(String) filed.get(student);
            System.out.println("反射私有属性修改了 name: "+name);
        } catch (ClassNotFoundException | NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    // 反射私有方法
    public static void reflectPrivateMethod(){
        Class c=null;
        try {
            // 获取对象
            c=Class.forName("demo.Student");
            // 获取私有方法
            Method method=c.getDeclaredMethod("function", String.class);
            // 通过获取到对象实例化
            Student student=(Student) c.newInstance();
            method.setAccessible(true);
            // 给实例化对象的私有方法 function 赋值
            method.invoke(student,"给私有方法 function 传参数");
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

6. 反射的优点和缺点

优点:

  • 对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
  • 增加程序的灵活性和扩展性,降低耦合性,提高自适力
  • 反射已经运用在了很多流行的阔加,如:Struts、Hibernate、Spring 等等

缺点:

  • 使用反射会导致程序的效率降低
  • 反射技术绕过源代码的技术,因而会带来维护的问题
  • 反射代码相比于直接的代码更加复杂

相关文章