Java 反射

1 反射概述

1.1 什么是反射

反射机制(Reflection)是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字,就可以通过反射机制来获取类的所有属性和方法。

反射的作用:

  • 可以动态查看一个类或对象的所有属性和方法,包括使用private修饰的属性和方法
  • 可以动态加载类
  • 可以动态的创建一个类的实例
  • 可以动态调用一个对象的方法

1.2 反射的优缺点

反射的优点:

  • 反射允许我们在程序运行期间获得类的信息并操作一个类中的方法,因此可以提高代码的灵活性和扩展性
  • 反射是Java中很多高级特性的基础,比如后面会介绍的注解、动态代理等特性
  • 在很多框架中,对反射技术的使用也非常多,比如大名鼎鼎的Spring框架、各类ORM框架、RPC框架等

反射的缺点:

  • 反射的代码的可读性和可维护性都比较低
  • 反射的代码执行的性能低

开发者应该在业务代码中尽量避免使用反射。但是,作为一个合格的Java开发者,需要具备读懂中间件和框架中反射代码的能力和使用反射解决特定问题的能力。

1.3 反射API

Java提供了反射相关的API,核心是java.lang.Class类,用于加载类和获取类的相关信息。

Java 反射 API 位于 java.lang.reflect 包中,主要包括:

  • Constructor类:用来描述一个类的构造方法
  • Field类:用来描述一个类的成员变量
  • Method类:用来描述一个类的方法
  • Modifier类:用来描述类内各元素的修饰符
  • Array:用来对数组进行操作

2 Class类

2.1 Class类概述

java.lang.Class类是Java反射机制的基础。从面向对象编程的角度,Class类是对Java程序中的"类"进行的抽象,每个Class的对象代表一个被JVM加载到内存中的"类"。

在程序运行时,JVM首先检查要加载的类对应的Class对象是否已经创建。如果没有创建,JVM会根据类名查找.class文件,将其加载到内存中,并创建相应的Class对象。如下图所示:

需要注意,Class类的构造器被设计为私有的,也就是说开发者不能主动创建Class类的对象。Class类的对象仅能由JVM创建。

2.2 类加载器

类加载器(Class Loader)是JVM的一个子系统,负责将class文件加载到内存中,然后在堆中创建一个代表这个类的Class对象,作为方法区中类数据的访问入口。

2.3 获取Class对象

开发者可以通过4种方式获取一个Java类的Class对象:

  • 调用对象的getClass()方法获取Class对象
  • 根据类名.class获取Class对象
  • 根据Class中的静态方法Class.forName()获取Class对象
  • 通过类加载器ClassLoader加载类并获取Class对象

编写代码,实现动态加载类。代码示意如下:

java 复制代码
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;

public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
        String str = "abc";
        // 调用对象的getClass()方法获取Class对象
        Class clazz1 = str.getClass();
        System.out.println(clazz1.getName());
        // 根据类名.class获取Class对象
        Class clazz2 = String.class;
        System.out.println(clazz2.getName());
        // 根据Class中的静态方法Class.forName()获取Class对象
        Class clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3.getName());
        // 通过类加载器ClassLoader加载类对象
        ClassLoader classLoader = ReflectDemo1.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("java.util.ArrayList");
        System.out.println(clazz4.getName());
    }
} 

2.4 动态创建对象

Class 提供了动态创建对象的方法:

java 复制代码
Object newInstance()

newInstance方法将调用类信息中的无参数构造器创建对象。由于该方法在异常处理上存在缺陷,故在Java 9版本开始被标记为过期方法。

在Java 9及后续版本中,可以通过Constructor的API来动态创建对象。

java 复制代码
clazz.getDeclaredConstructor().newInstance()

编写代码,调用无参构造器动态创建实例。代码示意如下:

java 复制代码
public class Student {
    private String name;
    private Integer age;
    // 教材中省略无参构造器,带参构造器,get/set方法和toString方法   
}

public class ReflectDemo2 {
    public static void main(String[] args) throws Exception {
        //1加载类对象
        ClassLoader classLoader = ReflectDemo2.class.getClassLoader();
        Class cls = classLoader.loadClass("jaf.day07.cases.reflect.Student");
        //2通过类对象实例化
        Object o1 = cls.newInstance();//调用无参构造器,已过期
        System.out.println(o1);
        Object o2 = cls.getDeclaredConstructor().newInstance();
        System.out.println(o2);
        // 3通过带参构造器实例化
        Object o3 = cls.getDeclaredConstructor(String.class, Integer.class)
                        .newInstance("Tom", 18);
        System.out.println(o3);
    }
}

2.5 动态调用方法

Class 可以动态获取方法:

java 复制代码
 Method getMethod() 

其中,返回的Method 代表方法信息,可以利用Method API获取方法对详细信息,如:方法名,返回值类型,参数类型列表等。

Method 还可以动态执行一个方法:

java 复制代码
Object invoke(Object obj, Object... args)
  • 参数1:obj 代表一个对象,该对象上一定包含当前方法,否则将出现调用异常;如果obj为null则抛出空指针异常
  • 参数2:args 代表调用方法时候传递的实际参数,如果没有参数可以不用或者传递null,但是要注意参数的个数和类型必须和要调用的方法匹配,否则将出现参数错误异常
  • 返回值:表示方法执行的结果,因为可能是任何类型,则其类型为Object,调用没有返回值的方法则返回值为null

当被调用方法执行出现异常时候抛出InvocationTargetException。

编写代码,动态调用无参方法。代码示意如下:

java 复制代码
import java.lang.reflect.Method;
import java.util.Scanner;
public class ReflectDemo3 {
    public static void main(String[] args) throws Exception {
        //实例化
        Class cls = Student.class;
        Object o = cls.getDeclaredConstructor().newInstance();
        //调用方法
        //1 通过类对象获取要调用的方法
        Method method = cls.getMethod("toString");
        //2 通过方法对象执行该方法
        Object result = method.invoke(o);
        System.out.println(result);
        //3 调用带参方法
        Method method1 = cls.getMethod("setName", String.class);
        method1.invoke(o, "Jerry");
        Method method2 = cls.getMethod("setAge", Integer.class);
        method2.invoke(o, 18);
        System.out.println(o);
    }
}
相关推荐
IT枫斗者枫哥10 分钟前
MyBatis一对多分页:LIMIT 20,为什么凑不齐20个订单?
java·数据库
代码方舟11 分钟前
Java数据工程:利用天远全网运营商三要素优化线上实名认证合规体验
java·人工智能
BBmmo11 分钟前
我的 Java 学习笔记 · 第 7 篇:泛型与通配符
java
独泪了无痕12 分钟前
Hutool之RandomUtil:随机数生成的终极利器
java·后端
花开路口12 分钟前
深入理解 Java/Kotlin 协变与逆变
java·kotlin
运行时异常12 分钟前
【WMS 仓储系统集成 AI Agent 实战】第 7 讲:Vue3 前端工程化——Token 刷新锁、Markdown 渲染踩坑与权限体系
java
她的男孩13 分钟前
开放接口限流从 20 改到 200 还是每分钟 20 次:拆完防重放+幂等+限流,我找到 5 个静默失效的坑
java·后端·架构
天天被压力19 分钟前
【Python 量化取数指南 #13】Python 把行情落库:sqlite 一键存,回测随用随取
java·人工智能·python
天天被压力19 分钟前
【Python 量化取数指南 #14】Python 清洗行情数据:复权停牌对齐,回测不翻车
java·人工智能·python
独泪了无痕23 分钟前
开发利器Hutool之MapUtil的使用
java·后端