找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3428

积分

0

好友

480

主题
发表于 昨天 05:13 | 查看: 4| 回复: 0

当程序需要在运行时发现对象和类的真实信息时,我们有两条路径可选。第一种,如果在编译时和运行时都完全知晓类型的具体信息,那事情就简单了:先用 instanceof 运算符判断,再强制转换类型即可。但更多时候,我们面对的是第二种情况——编译时根本无法预知对象和类可能属于哪些类型,程序只能依赖运行时信息来探索其真实面貌。这时,就必须请出 反射(Reflection) 这个强大的工具了。

获得 Class 对象

每个类被 JVM 加载后,系统都会为其生成一个对应的 Class 对象。这个 Class 对象就像是通往该类的“钥匙”,通过它,我们就能访问到 JVM 中的这个类。获取这把“钥匙”通常有三种方式:

  1. 使用 Class.forName(String className) 静态方法:需要传入一个字符串参数,即该类的全限定类名(包含包名)。
  2. 调用某个类的 .class 属性:例如 String.class。这种方式代码更安全,性能也更好,是大部分场景下的首选。
  3. 调用对象的 getClass() 方法:这是 java.lang.Object 类中的方法,所有 Java 对象都能调用,返回该对象所属类对应的 Class 对象。

拿到了 Class 对象,就等于掌握了分析这个类的“控制台”,接下来就可以调用其丰富的方法来获取详尽的类信息了。

获取 Class 中的信息

Class 类提供了大量实例方法来获取其对应类的详细信息。

获取 Class 对应类所包含的构造器

- Constructor<T> getConstructor(Class<?>... parameterTypes): 返回指定参数列表的 public 构造器。
- Constructor<?>[] getConstructors(): 返回所有 public 构造器。
- Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 返回指定参数列表的构造器(与访问权限无关)。
- Constructor<?>[] getDeclaredConstructors(): 返回所有构造器(与访问权限无关)。

获取 Class 对应类所包含的方法

- Method getMethod(String name, Class<?>... parameterTypes): 返回指定名称和参数列表的 public 方法。
- Method[] getMethods(): 返回所有 public 方法。
- Method getDeclaredMethod(String name, Class<?>... parameterTypes): 返回指定名称和参数列表的方法(与访问权限无关)。
- Method[] getDeclaredMethods(): 返回所有方法(与访问权限无关)。

访问 Class 对应类所包含的成员变量

- Field getField(String name): 返回指定名称的 public 成员变量。
- Field[] getFields(): 返回所有 public 成员变量。
- Field getDeclaredField(String name): 返回指定名称的成员变量(与访问权限无关)。
- Field[] getDeclaredFields(): 返回所有成员变量(与访问权限无关)。

访问 Class 对应类上所包含的 Annotation

- <A extends Annotation> A getAnnotation(Class<A> annotationClass): 获取指定类型的注解,不存在则返回 null。
- <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass): (Java 8+) 获取直接修饰该类的指定类型注解。
- Annotation[] getAnnotations(): 返回类上存在的所有注解。
- Annotation[] getDeclaredAnnotations(): 返回直接修饰该类的所有注解。
- <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass): 获取修饰该类的指定类型的多个注解(支持重复注解)。
- <A extends Annotation> A[] getDeclaredAnnotationsByType(Class<A> annotationClass): 获取直接修饰该类的指定类型的多个注解(支持重复注解)。

访问 Class 对象对应类里的内部类

Class<?>[] getDeclaredClasses(): 返回该类的全部内部类。

访问 Class 对象对应类里的外部类

Class<?> getDeclaringClass(): 返回该类所在的外部类。

访问 Class 对象对应类所实现的接口

Class<?>[] getInterfaces(): 返回该类所实现的全部接口。

访问 Class 对象对应类所继承的父类

Class<? super T> getSuperclass(): 返回该类的超类(父类)的 Class 对象。

获取 Class 对象对应类的修饰符、所在包、类名等基本信息

- int getModifiers(): 返回修饰符常量组合,需用 Modifier 工具类解码。
- Package getPackage(): 获取包信息。
- String getName(): 返回全限定类名字符串。
- String getSimpleName(): 返回类简称。

Class 对象判断该类的类型

- boolean isAnnotation(): 是否是注解类型。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass): 是否被指定注解修饰。
- boolean isAnonymousClass(): 是否是匿名类。
- boolean isArray(): 是否是数组类。
- boolean isEnum(): 是否是枚举类。
- boolean isInterface(): 是否是接口。
- boolean isInstance(Object obj): 判断 obj 是否是该 Class 的实例(可替代 instanceof)。

特殊说明

getMethod()getConstructor() 方法需要传入 Class<?>... 参数来区分重载。因为确定一个方法需要“方法名+形参类型列表”共同决定。构造器则只需“形参类型列表”,因为构造器名都相同。

例如,对于以下重载方法:

public void info()
public void info(String str)
public void info(String str, Integer num)

要获取第二个 info 方法,代码如下:

// 获取第二个info方法
// 前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class)

获取第三个 info 方法:

// 获取第三个info方法
// 前一个参数指定方法名,后面的个数可变的Class参数指定形参类型列表
clazz.getMethod("info", String.class, Integer.class)

下面是一个综合示例,展示了如何使用反射获取类的各种信息:

import java.util.*;
import java.lang.reflect.*;
import java.lang.annotation.*;

// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {}

@Retention(value = RetentionPolicy.RUNTIME)
@interface Annos {
    Anno[] value();
}

// 使用4个注解修饰该类
@SuppressWarnings(value = "unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest
{
    // 为该类定义一个私有的构造器
    private ClassTest()
    {
    }
    // 定义一个有参数的构造器
    public ClassTest(String name)
    {
        System.out.println("执行有参数的构造器");
    }
    // 定义一个无参数的info方法
    public void info()
    {
        System.out.println("执行无参数的info方法");
    }
    // 定义一个有参数的info方法
    public void info(String str)
    {
        System.out.println("执行有参数的info方法" + ",其str参数值:" + str);
    }
    // 定义一个测试用的内部类
    class Inner
    {
    }
    public static void main(String[] args)
        throws Exception
    {
        // 下面代码可以获取ClassTest对应的Class
        Class<ClassTest> clazz = ClassTest.class;
        // 获取该Class对象所对应类的全部构造器
        Constructor[] ctors = clazz.getDeclaredConstructors();
        System.out.println("ClassTest的全部构造器如下:");
        for (var c : ctors)
        {
            System.out.println(c);
        }
        // 获取该Class对象所对应类的全部public构造器
        Constructor[] publicCtors = clazz.getConstructors();
        System.out.println("ClassTest的全部public构造器如下:");
        for (var c : publicCtors)
        {
            System.out.println(c);
        }
        // 获取该Class对象所对应类的全部public方法
        Method[] mtds = clazz.getMethods();
        System.out.println("ClassTest的全部public方法如下:");
        for (var md : mtds)
        {
            System.out.println(md);
        }
        // 获取该Class对象所对应类的指定方法
        System.out.println("ClassTest里带一个字符串参数的info()方法为:" + clazz.getMethod("info", String.class));
        // 获取该Class对象所对应类的上的全部注解
        Annotation[] anns = clazz.getAnnotations();
        System.out.println("ClassTest的全部Annotation如下:");
        for (var an : anns)
        {
            System.out.println(an);
        }
        System.out.println("该Class元素上的@SuppressWarnings注解为:" + Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
        System.out.println("该Class元素上的@Anno注解为:" + Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
        // 获取该Class对象所对应类的全部内部类
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("ClassTest的全部内部类如下:");
        for (var c : inners)
        {
            System.out.println(c);
        }
        // 使用Class.forName方法加载ClassTest的Inner内部类
        Class inClazz = Class.forName("ClassTest$Inner");
        // 通过getDeclaringClass()访问该类所在的外部类
        System.out.println("inClazz对应类的外部类为:" + inClazz.getDeclaringClass());
        System.out.println("ClassTest的包为:" + clazz.getPackage());
        System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
    }
}

关键点Class 提供了获取构造器、方法、成员变量、内部类、注解等信息的全套功能。但请注意,@SuppressWarnings 注解的保留策略是 SOURCE(仅源代码级别),因此在运行时通过 Class 对象无法访问到它。

方法参数反射

Java 8 在 java.lang.reflect 包中新增了 Executable 抽象基类,它代表可执行的类成员(方法或构造器),是 ConstructorMethod 的父类。

Executable 提供了获取注解、判断可变参数、获取修饰符等方法,还有两个核心方法用于获取形参信息:

  • int getParameterCount(): 获取形参个数。
  • Parameter[] getParameters(): 获取所有形参。Parameter 是 Java 8 新增的 API,代表一个参数。

Parameter 对象本身也提供了丰富的方法:

  • getModifiers(): 获取形参修饰符。
  • String getName(): 获取形参名。
  • Type getParameterizedType(): 获取带泛型的形参类型。
  • Class<?> getType(): 获取形参类型。
  • boolean isNamePresent(): 返回 class 文件是否包含形参名信息。
  • boolean isVarArgs(): 判断是否为可变参数。

重要提示:默认情况下,javac 编译生成的 class 文件不包含方法形参名信息。因此 isNamePresent() 通常返回 falsegetName() 也得不到真实参数名。如果需要保留这些信息,必须在编译时使用 -parameters 选项。

下面是一个获取方法参数信息的例子:

import java.lang.reflect.*;
import java.util.*;

class Test
{
    public void replace(String str, List<String> list){}
}

public class MethodParameterTest
{
    public static void main(String[] args) throws Exception
    {
        // 获取Test的类
        Class<Test> clazz = Test.class;
        // 获取Test类的带两个参数的replace()方法
        Method replace = clazz.getMethod("replace",
            String.class, List.class);
        // 获取指定方法的参数个数
        System.out.println("replace方法参数个数:" + replace.getParameterCount());
        // 获取replace的所有参数信息
        Parameter[] parameters = replace.getParameters();
        var index = 1;
        // 遍历所有参数
        for (var p : parameters)
        {
            if (p.isNamePresent())
            {
                System.out.println("---第" + index++ + "个参数信息---");
                System.out.println("参数名:" + p.getName());
                System.out.println("形参类型:" + p.getType());
                System.out.println("泛型类型:" + p.getParameterizedType());
            }
        }
    }
}

要使上述代码能打印出参数名,必须使用 -parameters 选项编译:

javac -parameters -d . MethodParameterTest.java

然后执行程序,可以看到输出包含了具体的参数名和泛型信息:

D:\>java MethodParameterTest
replace方法参数个数:2
---第1个参数信息---
参数名:str
形参类型:class java.lang.String
泛型类型:class java.lang.String
---第2个参数信息---
参数名:list
形参类型:interface java.util.List
泛型类型:java.util.List<java.lang.String>

通过本文的梳理,相信你对 Java 反射的核心机制——特别是如何通过 Class 对象全方位操作类信息——有了更系统的认识。反射是框架设计的基石,深入理解其原理,能让你在阅读源码和进行高级开发时更加得心应手。如果你想进一步探讨反射的高级应用或遇到相关问题,欢迎到 云栈社区 与更多的开发者交流。




上一篇:MyBatis映射器高级应用:typeHandler、动态SQL、resultMap级联与二级缓存实战(Spring Boot + MySQL 8)
下一篇:谷歌Gemini 3.1 Pro发布:推理与编程性能显著提升,基准测试分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-23 10:25 , Processed in 0.904636 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表