当前位置: 首页 > news >正文

JavaSE反射篇

Java反射

一、反射是什么

反射(Reflection)是 Java 语言中的一种机制,允许运行中的程序对自身进行检查或 “自省”,并能直接操作程序的内部属性。也就是说,即使在编译时不知道类的具体信息,程序也能在运行时加载类,获取类的结构(包括属性、方法、构造函数等),并对类的对象进行操作。这么说可能有点抽象,我们先了解关于Java反射的api,掌握了这些api的使用,也就会使用反射了

在 Java 中,所有的类都是 Class 类的实例。通过 Class 类,我们可以获取类的各种信息,如类的名称、属性、方法、构造函数等。

获取 Class 对象主要有以下三种方式:

  1. 通过对象的 getClass() 方法
public class getClass {
    public static void main(String[] args) {
        String re1 = "这是第一个对象";
        String re2 = "这是第二个对象";
        Class<? extends String> ob =  re1.getClass();
        Class<? extends String> ob2 = re2.getClass();
        System.out.println(ob);
        System.out.println(ob2);
    }
}

其中re1re2都是String类的对象,因此输出结果相同,如下

class java.lang.String
class java.lang.String
  1. 通过类的 class 字面量
public class getClass {
    public static void main(String[] args) {
        System.out.println(String.class);
    }
}

无论基本类型或是自定义类,jvm都为其提供了一个静态成员class,可以直接对类进行访问

  1. 通过 Class.forName() 静态方法

先看一下forName的构造,其中className是类的全限定名(即包名.类名)

static Class<?> forName(String className)

forName可以结合配置文件properties结合使用,yin

public class getClass {
    public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream("C:\\Users\\30371\\IdeaProjects\\Review\\src\\test.properties");
        Properties prop = new Properties();
        prop.load(fis);
        String name = prop.getProperty("name");
        Class<?> aclass = Class.forName(name);
        System.out.println("aclass = " + aclass);
    }
}

二、反射的基本操作

(一)获取类的属性

通过 Class 对象,我们可以获取类的属性信息。例如,有一个简单的 Person 类:

public class Person {
    private String name;
    public int age;
    protected String address;
    String gender;
}

获取 Person 类属性的代码如下:

Class<?> clazz = Person.class;
// 获取所有公共属性(包括从父类继承的公共属性)
Field[] publicFields = clazz.getFields();
for (Field field : publicFields) {
    System.out.println("公共属性: " + field.getName());
}
// 获取所有本类声明的属性,包括私有、受保护和默认访问修饰符的属性,但不包括从父类继承的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
    System.out.println("本类声明的属性: " + field.getName());
}

getFields() 方法只能获取类的公共属性,包括从父类继承的公共属性;而 getDeclaredFields() 方法能获取类自身声明的所有属性,无论其访问修饰符是什么,但不包括从父类继承的属性,这也称暴力反射

(二)获取类的方法

同样以 Person 类为例,假设该类有如下方法:

public class Person {
    // 省略属性部分
    public void sayHello() {
        System.out.println("Hello!");
    }
    private void privateMethod() {
        System.out.println("This is a private method.");
    }
    public String getInfo(String prefix) {
        return prefix + " Name: " + name + ", Age: " + age;
    }
}

获取 Person 类方法的代码如下:

Class<?> clazz = Person.class;
// 获取所有公共方法(包括从父类继承的公共方法)
Method[] publicMethods = clazz.getMethods();
for (Method method : publicMethods) {
    System.out.println("公共方法: " + method.getName());
}
// 获取所有本类声明的方法,包括私有、受保护和默认访问修饰符的方法,但不包括从父类继承的方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
    System.out.println("本类声明的方法: " + method.getName());
}

类似地,getMethods() 方法获取公共方法,getDeclaredMethods() 方法获取类自身声明的所有方法。并且,我们还可以获取方法的参数类型、返回值类型等详细信息。例如获取 getInfo 方法的参数和返回值类型:

Method getInfoMethod = clazz.getMethod("getInfo", String.class);
Class<?> returnType = getInfoMethod.getReturnType();
Class<?>[] parameterTypes = getInfoMethod.getParameterTypes();
System.out.println("返回值类型: " + returnType.getName());
System.out.println("参数类型: ");
for (Class<?> parameterType : parameterTypes) {
    System.out.println(parameterType.getName());
}

(三)获取类的构造函数

继续以 Person 类为例,假设该类有不同的构造函数:

public class Person {
    // 省略属性和方法部分
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    private Person(String name) {
        this.name = name;
    }
}

获取 Person 类构造函数的代码如下:

Class<?> clazz = Person.class;
// 获取所有公共构造函数
Constructor<?>[] publicConstructors = clazz.getConstructors();
for (Constructor<?> constructor : publicConstructors) {
    System.out.println("公共构造函数: " + constructor.getName());
}
// 获取所有本类声明的构造函数,包括私有构造函数
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
    System.out.println("本类声明的构造函数: " + constructor.getName());
}

通过构造函数,我们可以在运行时创建类的实例。例如使用 public Person(String name, int age) 构造函数创建 Person 实例:

Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person = (Person) constructor.newInstance("John", 30);

(四)使用反射创建对象和调用方法

反射不仅可以获取类的信息,还能在运行时创建对象并调用对象的方法。

  1. 创建对象
Class<?> clazz = Person.class;
// 使用无参构造函数创建对象
Person person1 = (Person) clazz.newInstance();
// 通过指定构造函数创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person person2 = (Person) constructor.newInstance("Jane", 25);
  1. 调用方法
Person person = new Person("Bob", 35);
Class<?> clazz = person.getClass();
// 获取要调用的方法
Method sayHelloMethod = clazz.getMethod("sayHello");
// 调用方法
sayHelloMethod.invoke(person);
// 调用带参数的方法
Method getInfoMethod = clazz.getMethod("getInfo", String.class);
String result = (String) getInfoMethod.invoke(person, "Info: ");
System.out.println(result);

(五)访问私有成员

反射机制还可以突破访问修饰符的限制,访问类的私有成员。不过,在实际应用中,除非有特殊需求,否则不建议这样做,因为这破坏了类的封装性。
以访问 Person 类的私有属性 name 为例:

Person person = new Person("Alice", 40);
Class<?> clazz = person.getClass();
Field nameField = clazz.getDeclaredField("name");
// 打破私有访问限制
nameField.setAccessible(true);
try {
    String name = (String) nameField.get(person);
    System.out.println("私有属性 name 的值: " + name);
    nameField.set(person, "Updated Name");
    name = (String) nameField.get(person);
    System.out.println("修改后的私有属性 name 的值: " + name);
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

通过调用 Field 对象的 setAccessible(true) 方法,我们可以访问和修改私有属性。对于私有方法的调用,也可以采用类似的方式,先通过 getDeclaredMethod 获取私有方法,然后调用 setAccessible(true) 来调用该方法。

三、反射的优缺点

(一)优点

  1. 高度灵活性:反射允许程序在运行时动态地加载类、创建对象、调用方法和访问属性,这使得程序可以根据不同的运行时条件进行灵活的调整,大大提高了程序的通用性和扩展性。
  2. 增强代码的可维护性和可扩展性:通过反射,我们可以将一些配置信息(如类名、方法名等)外部化,例如存储在配置文件中。当需求发生变化时,只需要修改配置文件,而不需要修改大量的代码,降低了代码的维护成本,同时也方便了功能的扩展。
  3. 实现一些高级特性:如动态代理、对象序列化与反序列化、测试框架等,这些特性在 Java 编程中非常重要,而反射是实现它们的关键技术。

(二)缺点

  1. 性能问题:反射操作比直接的方法调用和属性访问要慢得多。因为反射涉及到在运行时查找类信息、方法信息和属性信息,并且需要进行额外的安全检查等操作。在对性能要求较高的场景中,频繁使用反射可能会导致性能瓶颈。
  2. 代码可读性和可维护性下降:使用反射的代码往往比直接调用的代码更复杂、更难以理解。反射代码中通常会使用大量的字符串来表示类名、方法名和属性名等,这使得代码的可读性变差,并且在编译时无法进行类型检查,容易出现运行时错误,增加了代码维护的难度。
  3. 破坏封装性:反射可以访问和修改类的私有成员,这破坏了类的封装性,违背了面向对象编程的基本原则。过度使用反射可能会导致代码的结构混乱,降低代码的可维护性和可测试性。

四、总结

Java 反射机制是一项强大而复杂的特性,它为 Java 程序带来了高度的动态性和灵活性,在框架开发、动态代理、对象序列化与反序列化、测试框架等众多领域有着广泛的应用。然而,反射也存在性能问题、代码可读性和可维护性下降以及破坏封装性等缺点。在实际编程中,我们应该根据具体的需求和场景,合理地使用反射机制,充分发挥其优势,同时尽量避免其带来的负面影响。希望通过本文,你能对 Java 反射机制有更深入的理解,并在今后的编程实践中能够熟练运用反射来解决实际问题。

相关文章:

  • python练习题
  • OSPFv3 的 LSA 详解
  • 青少年编程与数学 02-014 高中数学知识点 01课题、概要
  • 华为机试—密码验证合格程序
  • GLSL(OpenGL 着色器语言)基础语法
  • 云计算初识
  • 如何使不同的窗体控件,适应不同分辨率的屏幕?
  • 从零开始:Windows 系统中 PowerShell 配置 FFmpeg 的详细步骤
  • 基于javaweb的SpringBoot驾校预约学习系统设计与实现(源码+文档+部署讲解)
  • Mysql 索引性能分析
  • 欢迎使用Markdown编辑器
  • 职能型组织、项目型组织、矩阵型组织的介绍及优缺点比较
  • 华为OD机试2025A卷 - 正则表达式替换(Java Python JS C++ C )
  • NX/UG二次开发—CAM获取加工操作的最低Z深度值的方法
  • 【数据结构篇】算法征途:穿越时间复杂度与空间复杂度的迷雾森林
  • 基于javaweb的SpringBoot实验室管理系统设计与实现(源码+文档+部署讲解)
  • 【差分隐私相关概念】差分隐私中的稀疏向量技术
  • Java虚拟机JVM知识点(持续更新)
  • 解决element plus el-dialog 被el-header覆盖问题
  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的 AOP:实现日志记录与性能监控
  • 苏州专业做网站公司电话/百度指数是搜索量吗
  • wordpress的插件名/旺道seo营销软件
  • 电信宽带办理/搜索引擎优化培训
  • 武汉住房和城乡建设厅网站/班级优化大师app下载学生版
  • 网站开发实训教程/没干过网络推广能干吗
  • 辽宁pc网站建设开发/关键词整站排名优化