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

【java】反射

反射

反射机制可以读取注解。

反射的概念

在 Java 中,通常情况下,我们在编译时就知道要使用的类和方法。但反射机制打破了这种常规,它允许程序在运行时动态地分析类、调用方法、操作字段等。也就是说,在运行时,程序可以根据需要来决定要使用哪个类、调用哪个方法、访问哪个字段,而不是在编译时就确定下来。

反射的核心类

Java 反射机制主要涉及以下几个核心类:

1、Class

Class类是反射机制的基础,它代表一个类或接口。在 Java 中,每个类都有一个对应的Class对象,通过Class对象可以获取类的各种信息,如类名、父类、接口、方法、字段等**(可以理解为Class对象拥有对应类的所有信息)**。获取Class对象的常见方式有以下几种:

  • Class.forName("全限定类名"):通过类的全限定名获取 Class 对象。
  • 类名.class:直接通过类名获取 Class 对象。
  • 对象.getClass():通过对象的 getClass() 方法获取 Class 对象。
// 示例代码
try {
    // 方式一:使用 Class.forName
    Class<?> clazz1 = Class.forName("java.util.ArrayList");
    // 方式二:使用 类名.class
    Class<?> clazz2 = java.util.ArrayList.class;
    // 方式三:使用 对象.getClass()
    java.util.ArrayList list = new java.util.ArrayList();
    Class<?> clazz3 = list.getClass();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2、Constructor:代表类的构造函数,通过 Class 对象的 getConstructor()getDeclaredConstructor() 方法可以获取构造函数对象,然后使用 newInstance() 方法创建类的实例。

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorExample {
    public static void main(String[] args) {
        try {
            Class<?> clazz = String.class;
            // 获取带有一个 String 参数的构造函数
            Constructor<?> constructor = clazz.getConstructor(String.class);
            // 创建对象
            Object obj = constructor.newInstance("Hello");
            System.out.println(obj);
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

3、Method:代表类的方法,通过 Class 对象的 getMethod()getDeclaredMethod() 方法可以获取方法对象,然后使用 invoke() 方法调用该方法。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MethodExample {
    public static void main(String[] args) {
        try {
            Class<?> clazz = String.class;
            // 获取 substring 方法
            Method method = clazz.getMethod("substring", int.class, int.class);
            String str = "HelloWorld";
            // 调用方法
            Object result = method.invoke(str, 0, 5);
            System.out.println(result);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

4、Field:代表类的字段,通过 Class 对象的 getField()getDeclaredField() 方法可以获取字段对象,然后使用 get()set() 方法访问和修改字段的值。

import java.lang.reflect.Field;

class MyClass {
    public int myField = 10;
}

public class FieldExample {
    public static void main(String[] args) {
        try {
            MyClass obj = new MyClass();
            Class<?> clazz = obj.getClass();
            // 获取字段
            Field field = clazz.getField("myField");
            // 获取字段的值
            int value = (int) field.get(obj);
            System.out.println("Original value: " + value);
            // 修改字段的值
            field.set(obj, 20);
            int newValue = (int) field.get(obj);
            System.out.println("New value: " + newValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

反射的使用场景

  1. 框架开发:许多 Java 框架(如 Spring、Hibernate 等)都广泛使用反射机制。例如,Spring 框架通过反射实现依赖注入,在运行时动态地创建对象并注入依赖;Hibernate 框架通过反射来映射数据库表和 Java 类。
  2. 插件化开发:在插件化开发中,可以使用反射机制动态加载和使用插件类,实现程序的扩展性。
  3. 单元测试:在单元测试中,可以使用反射机制访问和调用类的私有方法和字段,方便进行测试。

反射的优缺点

优点

  • 灵活性高:反射机制允许程序在运行时动态地处理类和对象,提高了程序的灵活性和可扩展性。
  • 可扩展性强:可以在不修改现有代码的情况下,通过反射机制动态地加载和使用新的类和方法。

缺点

  • 性能开销大:反射涉及到动态解析类和方法,相比直接调用,会带来一定的性能开销。
  • 安全性问题:反射机制可以访问和修改类的私有成员,可能会破坏类的封装性,带来安全隐患。
  • 代码可读性和维护性差:反射代码通常比较复杂,可读性和维护性较差。

得到Class类的几种方式

1. 使用 Class.forName() 方法

  • 原理Class.forName() 是一个静态方法,它接收一个字符串参数,该参数为类的全限定名(包含包名)。此方法会尝试加载指定名称的类,并返回对应的 Class 对象。如果指定的类不存在或者在加载过程中出现问题,会抛出 ClassNotFoundException 异常。
  • 适用场景:常用于动态加载类,例如在配置文件中指定类名,程序在运行时根据配置文件中的类名动态加载相应的类。
  • 示例代码
public class ForNameExample {
    public static void main(String[] args) {
        try {
            // 通过全限定名获取 Class 对象
            Class<?> clazz = Class.forName("java.util.ArrayList");
            System.out.println(clazz.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2. 使用 类名.class 语法

  • 原理:在 Java 中,每个类都有一个隐含的静态成员变量 class,通过 类名.class 可以直接获取该类对应的 Class 对象。这种方式是在编译时就确定了要获取的 Class 对象,不需要进行类的加载操作。
  • 适用场景:当在代码中明确知道要操作的类时,使用这种方式非常简洁和方便。
  • 示例代码
public class ClassLiteralExample {
    public static void main(String[] args) {
        // 直接通过类名获取 Class 对象
        Class<?> clazz = java.util.ArrayList.class;
        System.out.println(clazz.getName());
    }
}

3. 使用对象的 getClass() 方法

  • 原理Object 类是所有类的父类,Object 类中定义了 getClass() 方法,该方法返回调用该方法的对象所属类的 Class 对象。因此,任何 Java 对象都可以调用 getClass() 方法来获取其所属类的 Class 对象。
  • 适用场景:当已经有一个对象实例,需要获取该对象所属类的信息时,使用这种方式很合适。
  • 示例代码
import java.util.ArrayList;

public class GetClassExample {
    public static void main(String[] args) {
        // 创建对象
        ArrayList<String> list = new ArrayList<>();
        // 通过对象的 getClass() 方法获取 Class 对象
        Class<?> clazz = list.getClass();
        System.out.println(clazz.getName());
    }
}

4. 使用基本数据类型的 TYPE 字段

  • 原理:对于基本数据类型(如 intchar 等),Java 为它们提供了对应的包装类,并且每个包装类都有一个静态常量 TYPE,该常量是一个 Class 对象,代表对应的基本数据类型。
  • 适用场景:在处理基本数据类型和包装类的反射操作时会用到。
  • 示例代码
public class PrimitiveTypeExample {
    public static void main(String[] args) {
        // 获取 int 基本数据类型的 Class 对象
        Class<?> intClass = int.class;
        System.out.println(intClass.getName());

        // 通过包装类的 TYPE 字段获取 int 基本数据类型的 Class 对象
        Class<?> intClass2 = Integer.TYPE;
        System.out.println(intClass2.getName());
    }
}

哪些类型可以有Class对象

1. 类(class

普通的 Java 类,无论是自定义类还是 Java 标准库中的类,都有对应的 Class 对象。

// 自定义类
class MyClass {}

public class ClassForClassExample {
    public static void main(String[] args) {
        // 获取自定义类的 Class 对象
        Class<?> myClassClass = MyClass.class;
        System.out.println(myClassClass.getName());

        // 获取 Java 标准库中类的 Class 对象
        Class<?> stringClass = String.class;
        System.out.println(stringClass.getName());
    }
}

2. 接口(interface

Java 中的接口同样拥有对应的 Class 对象。

// 定义一个接口
interface MyInterface {}

public class ClassForInterfaceExample {
    public static void main(String[] args) {
        // 获取接口的 Class 对象
        Class<?> myInterfaceClass = MyInterface.class;
        System.out.println(myInterfaceClass.getName());

        // 获取 Java 标准库中接口的 Class 对象
        Class<?> listInterfaceClass = java.util.List.class;
        System.out.println(listInterfaceClass.getName());
    }
}

3. 数组(array

无论数组的元素类型是基本数据类型还是引用类型,数组都有对应的 Class 对象。数组的 Class 对象的名称包含元素类型和维度信息。

public class ClassForArrayExample {
    public static void main(String[] args) {
        // 基本数据类型数组
        int[] intArray = new int[5];
        Class<?> intArrayClass = intArray.getClass();
        System.out.println(intArrayClass.getName());

        // 引用类型数组
        String[] stringArray = new String[5];
        Class<?> stringArrayClass = stringArray.getClass();
        System.out.println(stringArrayClass.getName());
    }
}

4. 基本数据类型(primitive type

Java 的 8 种基本数据类型(byteshortintlongfloatdoublecharboolean)都有对应的 Class 对象。可以通过 .class 语法或者包装类的 TYPE 字段来获取。

public class ClassForPrimitiveExample {
    public static void main(String[] args) {
        // 通过 .class 语法获取基本数据类型的 Class 对象
        Class<?> intClass = int.class;
        System.out.println(intClass.getName());

        // 通过包装类的 TYPE 字段获取基本数据类型的 Class 对象
        Class<?> doubleClass = Double.TYPE;
        System.out.println(doubleClass.getName());
    }
}

5. 枚举(enum

枚举类型是一种特殊的类,也有对应的 Class 对象。

// 定义一个枚举
enum MyEnum {
    VALUE1, VALUE2
}

public class ClassForEnumExample {
    public static void main(String[] args) {
        // 获取枚举的 Class 对象
        Class<?> myEnumClass = MyEnum.class;
        System.out.println(myEnumClass.getName());
    }
}

6. 注解(annotation

注解是 Java 5 引入的一种元数据机制,注解类型也有对应的 Class 对象。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation {}

public class ClassForAnnotationExample {
    public static void main(String[] args) {
        // 获取注解的 Class 对象
        Class<?> myAnnotationClass = MyAnnotation.class;
        System.out.println(myAnnotationClass.getName());
    }
}

7. void 类型

void 类型也有对应的 Class 对象,可通过 void.class 来获取。

public class ClassForVoidExample {
    public static void main(String[] args) {
        // 获取 void 类型的 Class 对象
        Class<?> voidClass = void.class;
        System.out.println(voidClass.getName());
    }
}

相关文章:

  • SAP S/4 HANA 升级带来的 3个黄金周期
  • PointVLA:将 3D 世界注入视觉-语言-动作模型
  • 怎么用LoRA的低秩结构近似Fisher矩阵
  • Pytorch使用手册—扩展 TorchScript 使用自定义 C++ 操作符(专题五十三)
  • 华为云虚拟化技术
  • ffmpeg(库编译) 01 搭建环境和安装依赖
  • Java SE 24 新增特性
  • C语言:(大数相加版)数字阶梯求和
  • JAVA-AOP底层原理
  • 每日一题——买卖股票的最佳时机
  • SQL Server数据库慢SQL调优
  • 《认知觉醒》改变的核心方法论
  • 【Java基础巩固系列】异常
  • 【Android】安卓 Java下载ZIP文件并解压(笔记)
  • Python中Requests的Cookies的简单使用
  • 2025-03-19 学习记录--C/C++-C语言-单链表的结构体定义 + LNode * 和 LinkList 的区别
  • 【深度学习】多目标融合算法(五):定制门控网络CGC(Customized Gate Control)
  • 【工具类】Java的 LocalDate 获取本月第一天和最后一天
  • Linux killall 命令使用详解
  • Springboot项目集成maven-assembly-plugin进行打包
  • 首日5金!中国队夺得跳水世界杯总决赛混合团体冠军
  • 孙一凡的东欧狂想音乐会:一场穿越东欧的听觉绮梦
  • 美“群聊泄密门”始作俑者沃尔兹将离职
  • 国务院食安办:加强五一假期食品生产、销售、餐饮服务环节监管
  • 启程回家!神十九轨道舱与返回舱成功分离
  • 王毅:时代不容倒退,公道自在人心