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);
    }
}
相关推荐
蓝黑20204 分钟前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深6 分钟前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__11 分钟前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞15 分钟前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货17 分钟前
Rust 的简介
开发语言·后端·rust
shuangrenlong17 分钟前
slice介绍slice查看器
java·ubuntu
牧竹子18 分钟前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
湫ccc25 分钟前
《Python基础》之基本数据类型
开发语言·python
Matlab精灵26 分钟前
Matlab函数中的隐马尔可夫模型
开发语言·matlab·统计学习
Microsoft Word26 分钟前
c++基础语法
开发语言·c++·算法