【后端之旅】源码分析 Class 篇

143 阅读11分钟

在 Java 中一切皆是类(Class)。我们习惯说 Java 的体系是面向对象编程。其实,Java 是面向类编程!所以再怎么了解 Class 本身也不会过。

关键字:Class, Java, Java Class, Java 源码

静态方法

  • Class.forName(String className) - 根据类的全限定名加载并初始化类。常用于动态加载类。

    频繁调用此方法可能影响性能,应考虑缓存 Class 对象。

    // 或 ClassLoader loader = Thread.currentThread().getContextClassLoader();
    ClassLoader loader = this.getClass().getClassLoader()
    
    // 不初始化类,而是缓存 Class 对象
    Class<?> clazz = Class.forName("com.example.MyClassName", false, loader);
    
  • Class.forName(String name, ClassLoader loader) - 根据类的全限定名,使用特定的类加载器加载并初始化类。

  • Class.forName(String name, boolean initialize, ClassLoader loader) - 根据类的全限定名,使用特定的类加载器加载,并依据 initialize 是否初始化类。

实例方法

  • Class.instance.toString() - 将 class 对象转换为字符串。

    // 也可以使用以下方式调用。后面的所有实例方法皆是。
    object.getClass().toString()
    
  • Class.instance.toGenericString() - 返回一个描述该类型(类、接口、修饰符、枚举)的字符串信息,包括:类型修饰器、类型名称、全名。

  • Class.instance.newInstance() - 返回 class 对象所表示的类的实例。此类本身不能是 Class、接口 等无法实例化的类。

  • Class.instance.isInstance(Object obj) - 判断对象是否为 class 对象所表示的类(或 子孙类)的实例。

  • Class.instance.isAssignableFrom(Class<?> cls) - 判断 class 对象所表示的类/接口是否与给定的 class 对象所表示的类/接口相同,或是其超类/超接口。常用于检查类的类型兼容性。

  • Class.instance.isInterface() - 判断 class 对象所表示的类型是否为一个接口。

  • Class.instance.isArray() - 判断 class 对象所表示的类型是否为一个数组。

  • Class.instance.isPrimitive() - 判断 class 对象所表示的类型是否为一个原始类(如 Boolean、Integer、Void 等)。

  • Class.instance.isAnnotation() - 判断 class 对象所表示的类型是否为一个注解。

  • Class.instance.isSynthetic() - 判断 class 对象所表示的类型是否为一个合成类。

    合成类是合成成员的一种,包括了合成类、合成方法、合成字段,用于支持 Java 的内部机制。

    合成成员:由 Java 编译器自动生成的(而非开发者显式编写的)类、方法、字段。如:

    1. 嵌套类访问外部类私有成员 时生成的访问器方法;
    2. 枚举类 自动生成的 values() 和 valueOf() 方法;
    3. try-with-resources 语句生成的额外代码;
    4. lambda 表达式方法引用 相关的生成类;
    5. 协变返回类型 桥接方法。
  • Class.instance.getName() - 返回 class 对象所表示的类型的名称字符串。其中每一维数组会用一个 [ 表示。八个基本类型的数组由 [ + 各自独立的大写字母 表示。类或接口的数组则由 [L + classname + ; 表示。

  • Class.instance.getClassLoader() - 判断 class 对象所表示的类型的加载器。如果类型是原始类型或 void,则返回 null

  • Class.instance.getTypeParameters() - 获取 class 对象所表示的类自身声明的泛型类型参数。对于原始类型或经过类型擦除的类,可能返回空数组。

    // 定义泛型类
    class MyGenericClass<K, V> {}
    
    // ...
    
    // 获得 [K, V]
    MyGenericClass.class.getTypeParameters();
    
  • Class.instance.getSuperclass() - 获取 class 对象所表示的类的父类/超类。返回的是一个 Class 对象,但它不会包含泛型信息(即使父类是泛型类)。

    如果需要获取父类的泛型信息,请使用 Class.instance.getGenericSuperclass()

    接口和基本类型的超类为 null。对于数组类型,始终返回 Object.class。代理类(Proxy)的超类是 Proxy.class

  • Class.instance.getGenericSuperclass() - 获取 class 对象所表示的类的父类/超类。返回的是一个 Class 对象,如果父类是泛型类,则对象会包含泛型信息。

  • Class.instance.getPackage() - 获取 class 对象所表示的类的包路径。结果是一个字符串,格式为:package ${包路径}

  • Class.instance.getInterfaces() - 获取 class 对象所表示的类直接实现的所有接口(即 不包括从父类继承的接口)。

  • Class.instance.getGenericInterfaces() - 获取 class 对象所表示的类直接实现的所有接口(即 不包括从父类继承的接口)。这些接口会保留泛型信息

  • Class.instance.getComponentType() - 获取 class 对象所表示的数组的类型。返回的是一个 Class 对象,对于非数组类型,则返回 null

    int[].class.getComponentType();       // 返回 int.class
    double[][].class.getComponentType();  // 返回 double[].class
    String[].class.getComponentType();    // 返回 String.class
    Object[][].class.getComponentType();  // 返回 Object[].class
    String.class.getComponentType();      // 返回 null
    
  • Class.instance.getModifiers() - 获取 class 对象所表示的类/接口的修饰符。返回一个整数,结果是全部修饰符所代表的值的总和。

    修饰符十六进制值十进制值
    public0x00011
    private0x00022
    protected0x00044
    static0x00088
    final0x001016
    synchronized0x002032
    volatile0x004064
    transient0x0080128
    native0x0100256
    interface0x0200512
    abstract0x04001024
    strictfp0x08002048
    synthetic0x10004096
    annotation0x20008192
    enum0x400016384

    由上表我们理解了,修饰符是以位掩码的形式存储的。

    要解析所返回的整数值(即为了获取完整的修饰符字符串),可以使用 Modifier.toString(int mod) 方法。要检查特定的修饰符,可以使用 Modifier.isPublic(int mod) 等方法。

  • Class.instance.getSigners() - 获取 class 对象所表示的(已签名)类的签名者信息。返回与类签名相关的对象数组(通常是 CodeSignerCertificate 对象),主要用于 Java 安全机制中的类签名验证。

  • Class.instance.getEnclosingMethod() - 获取直接包含 class 对象所表示类的声明方法(如果该类是在方法中声明的局部类或匿名类)。返回的是一个方法对象。对于其他类型的类(如顶级类、静态嵌套类等),返回 null

  • Class.instance.getEnclosingConstructor() - 获取直接包含 class 对象所表示类的声明构造器(如果该类是在构造器中声明的局部类或匿名类)。对于其他类型的类则返回 null

  • Class.instance.getDeclaringClass() - 获取直接包含 class 对象所表示类的直接外层类(仅对成员类/接口/注解/枚举有效)。常用于检查一个类是否是另一个类的正式成员。

  • Class.instance.getEnclosingClass() - 获取词法上直接包含 class 对象所表示类的声明类(相比较于 getDeclaringClass()getEnclosingClass() 适用于更多场景,包括局部类和匿名类)。常用于获取类定义的实际上下文环境。

    // 声明:
    class Outer {
        static class StaticNested {}           // 静态成员类
        
        void method() {
            class Local {}                     // 局部类
            Runnable anon = new Runnable() {}; // 匿名类
        }
    }
    
    // 测试:
    StaticNested.class.getDeclaringClass();  // 返回 Outer.class
    StaticNested.class.getEnclosingClass();  // 返回 Outer.class
    
    Local.class.getDeclaringClass();         // 返回 null
    Local.class.getEnclosingClass();         // 返回 Outer.class
    
    anon.getClass().getDeclaringClass();     // 返回 null
    anon.getClass().getEnclosingClass();     // 返回 Outer.class
    
  • Class.instance.getSimpleName() - 获取 class 对象所表示的类的最简名称(不含包名和其他修饰信息):

    • 对于普通类:返回类名本身(不含包名)
    • 对于嵌套类:返回嵌套类名(不含外部类名和包名)
    • 对于数组:返回元素类型的简单名称加 []
    • 对于匿名类:返回空字符串 ""
    • 对于基本类型:返回类型关键字,如 "int"
  • Class.instance.getTypeName() - 获取 class 对象所表示的类/接口的类型名称。

    • 对于普通类和接口:返回与 getCanonicalName() 相同的结果(全限定名)
    • 对于数组类型:返回格式为元素类型名 + []的字符串
    • 对于基本类型:返回关键字名称,如 "int"
    • 对于匿名类/局部类:返回与 getName() 相似的结果,如 Main$1
  • Class.instance.getCanonicalName() - 获取 class 对象所表示的类/接口的规范名称(完全限定类名)。

    • 对于普通类/接口:返回标准的全限定名,如 java.lang.String
    • 对于成员类/嵌套类:使用点 . 分隔外部类和内部类,如 java.util.Map.Entry
    • 对于数组:使用易读的 [] 表示法,如 java.lang.String[]
    • 对于基本类型:返回关键字名称,如 int
    • 对于匿名类/局部类:返回 null(因为它们没有规范名称)
  • Class.instance.isAnonymousClass() - 获取 class 对象所表示的类是否为匿名类。

  • Class.instance.isLocalClass() - 获取 class 对象所表示的类是否为局部类(在方法内部定义的带名类)。

  • Class.instance.isMemberClass() - 获取 class 对象所表示的类是否为成员类。

  • Class.instance.getClasses() - 获取 class 对象所表示的类(及其从父类/接口继承)的公共成员类/公共成员接口。常用于扫描可用的公共成员类。返回一个 Class 对象数组:

    1. 当前类中声明的所有 public 成员类和接口
    2. 从父类继承的所有 public 成员类和接口
    3. 从接口继承的所有 public 成员类和接口

    请注意:匿名类不会出现在结果中。方法内定义的局部类不会包含在结果中。

  • Class.instance.getFields() - 获取 class 对象所表示的类/接口(及其从父类/接口继承)的所有公共字段。返回一个包含以下内容的 Field 对象数组:

    1. 当前类/接口中声明的所有 public 字段
    2. 从父类继承的所有 public 字段
    3. 从接口继承的所有 public 字段

    请注意:返回的字段顺序与声明顺序无关。

  • Class.instance.getMethods() - 获取 class 对象所表示的类/接口(及其从父类/接口继承)的所有公共方法。返回一个包含以下内容的 Method 对象数组:

    1. 当前类/接口中声明的所有 public 方法
    2. 从父类继承的所有 public 方法
    3. 从接口继承的所有 public 方法
    4. 包括 Object 类的公共方法

    请注意:返回的方法顺序与声明顺序无关。

  • Class.instance.getConstructors() - 获取 class 对象所表示的类的所有公共构造函数。返回一个包含以下内容的 Constructor 对象数组:

    1. 当前类/接口中声明的所有 public 构造函数

    请注意:本方法不会返回父类的构造函数(构造函数也不能被继承)。

  • Class.instance.getField(String name) - 通过名称获取 class 对象所表示的类的特定公共字段(包括从父类和接口继承的公共字段)。如果子类和父类有同名字段,返回子类字段。

  • Class.instance.getMethod(String name, Class<?>... parameterTypes) - 通过方法名和参数类型获取 class 对象所表示的类的特定公共方法(包括从父类和接口继承的公共方法)。如果子类和父类有同名方法,返回子类方法。

  • Class.instance.getMethod(String name, Class<?>... parameterTypes) - 通过参数类型获取 class 对象所表示的类的特定公共构造函数。

    请注意:本方法不会返回父类的构造函数(构造函数也不能被继承)。

  • Class.instance.getDeclaredClasses() - 获取 class 对象所表示的类的成员类/公共成员接口。返回一个 Class 对象数组:

    1. 当前类中声明的所有 成员类和接口,包括:静态成员类、非静态成员类、接口、注解、枚举

    请注意:从父类继承的成员类、从接口继承的成员类、匿名类、局部类不会出现在结果中。

  • Class.instance.getDeclaredFields() - 获取 class 对象所表示的类的所有字段。返回一个 Field 对象数组:

    1. 当前类中声明的所有 字段,但不包括从父类或接口继承的字段
  • Class.instance.getDeclaredMethods() - 获取 class 对象所表示的类的所有方法。返回一个 Method 对象数组:

    1. 当前类中声明的所有 方法,但不包括从父类或接口继承的方法
  • Class.instance.getDeclaredConstructors() - 获取 class 对象所表示的类的所有构造函数。返回一个 Constructor<?> 对象数组:

    1. 当前类中声明的所有 构造函数,但不包括父类的构造函数(因为构造函数不能被继承)
  • Class.instance.getDeclaredField(String name) - 通过名称获取 class 对象所表示的类的特定字段。

  • Class.instance.getDeclaredMethod(String name, Class<?>... parameterTypes) - 通过名称和参数类型获取 class 对象所表示的类的特定方法。

  • Class.instance.getDeclaredConstructor(Class<?>... parameterTypes) - 通过参数类型获取 class 对象所表示的类的特定构造函数。

  • Class.instance.getResourceAsStream(String name) - 通过名称加载与 class 对象所表示的类位于相同位置的资源文件。返回可用于读取资源的 InputStream (资源不存在时则返回 null)。路析规则:

    路径格式查找位置
    "filename"与类同包下的资源(如 com/example/MyClass.class → com/example/filename
    "/filename"类路径根目录下的资源,即绝对路径(推荐使用)
    "subdir/filename"类所在包下的子目录资源

    请注意:

    • 确认资源文件已正确打包到 JAR/WAR
    • 资源找不到时,检查模块的 module-info.java 是否开放了资源包
    • 在 Windows 系统中仍使用 / 作为分隔符
    • 确保及时关闭 InputStream
    • 最好使用 try-with-resources 语法
  • Class.instance.getResource(String name) - 通过名称获取与 class 对象所表示的类位于相同位置的资源文件的 URL 引用。返回一个 java.net.URL 对象。常用于获取资源精确位置或资源的 URL 特性(如获取协议、主机等信息)。

    URL jarResource = MyClass.class.getResource("/assets/images/logo.jpg");
    // 可能返回:jar:file:/path/to/app.jar!/assets/images/logo.jpg
    
  • Class.instance.getProtectionDomain() - 获取 class 对象所表示的类的保护域(ProtectionDomain)信息。返回一个 java.security.ProtectionDomain 对象。该对象包括:

    1. 代码来源(CodeSource):类文件的来源位置(一个 URL)和证书
    2. 权限集合(Permissions):该类被授予的所有权限
    3. 类加载器(ClassLoader):加载该类的类加载器
    4. 主体(Principals):关联的安全主体(Java 2 安全模型)
  • Class.instance.desiredAssertionStatus() - 检查 class 对象所表示的类是否应该启用断言功能。返回一个布尔值。断言状态由以下因素决定:

    1. JVM 启动参数

      • -ea 或 -enableassertions:启用断言
      • -da 或 -disableassertions:禁用断言,默认值
      • 可针对特定包或类设置(如 -ea:com.example...
    2. 类加载器默认状态

      • 可通过 ClassLoader.setDefaultAssertionStatus() 设置,用于设置默认断言状态
    3. 包默认状态

      • 可通过 ClassLoader.setPackageAssertionStatus() 设置,用于设置整个包的断言状态
  • Class.instance.isEnum() - 检查 class 对象所表示的类是否为枚举类型。返回一个布尔值。对于数组和基本类型总是返回 false

  • Class.instance.getEnumConstants() - 获取 class 对象所表示的枚举类的所有常量值(所返回数组中的元素顺序与声明顺序一致),返回类型与枚举类型匹配。对于非枚举类型,则返回 null

  • Class.instance.cast(Object obj) - 将对象安全地转换为 class 对象所表示的类或接口类型。返回一个类或接口对象(前提是 objnull 或可以转换为 Class 对象表示的类型),否则抛出类转换异常。

    与强制类型转换相比,本方法的最大优点是保留了泛型,而前者会出现类型擦除。

  • Class.instance.asSubclass(Class<U> clazz) - 将 clazz 类对象安全地转换为 class 对象所表示的类的子类,返回一个 Class 对象。

    // 插件加载框架中的类型验证
    public <T> T loadPlugin(String className, Class<T> pluginBaseClass) throws Exception {
        // 加载类
        Class<?> loadedClass = Class.forName(className);
        // 验证是否为要求的基类/接口的子类
        Class<? extends T> pluginClass = loadedClass.asSubclass(pluginBaseClass);
        
        // 创建实例
        return pluginClass.getConstructor().newInstance();
    }
    
    // 使用示例
    Runnable plugin = loadPlugin("com.example.MyPlugin", Runnable.class);
    
  • Class.instance.getAnnotation(Class<A> annotationClass) - 获取 class 对象所表示的类上指定类型的注解(包括继承了父类和接口的注解,只要这注解声明了 @Inherited 元注解)。返回一个注解对象,如果不存在则返回 null

    // 实现一个通用的注解处理器
    public void processAnnotations(Class<?> clazz) {
        // 检查是否标记了弃用注解
        Deprecated deprecated = clazz.getAnnotation(Deprecated.class);
        if (deprecated != null) {
            System.out.println("警告: " + clazz.getName() + " 已弃用");
        }
    
        // 处理自定义注解
        Service serviceAnno = clazz.getAnnotation(Service.class);
        if (serviceAnno != null) {
            registerService(clazz, serviceAnno.value());
        }
    }
    
  • Class.instance.isAnnotationPresent(Class<? extends Annotation> annotationClass) - 判断 class 对象所表示的类是否标记了特定注解(不包括元注解)。

  • Class.instance.getAnnotationsByType(Class<A> annotationClass) - 处理标记 class 对象所表示的类上的 可重复注解@Repeatable)。返回注解实例数组(没有相关注解则返回空数组)。如果父类或接口的可重复注解标记了 @Inherited 元注解,那么被继承的这些注解也会被处理并返回。

  • Class.instance.getAnnotations() - 获取 class 对象所表示的类上的全部注解(包括继承了父类和接口的注解,只要这些注解声明了 @Inherited 元注解)。

    // 注解处理器框架中的通用处理
    public void processClassAnnotations(Class<?> clazz) {
        for (Annotation ann : clazz.getAnnotations()) {
            if (ann instanceof Service) {
                registerService(clazz);
                continue;
            }
            if (ann instanceof Component) {
                registerComponent(clazz);
                continue;
            }
            // 可以继续处理其他注解类型...
        }
    }
    
  • Class.instance.getDeclaredAnnotation(Class<A> annotationClass) - 获取 class 对象所表示的类上指定类型的注解(不包括继承了父类和接口的注解)。返回一个注解对象,如果不存在则返回 null

  • Class.instance.getDeclaredAnnotationsByType(Class<A> annotationClass) - 用于处理标记 class 对象所表示的类上的 可重复注解@Repeatable)。返回注解实例数组(没有相关注解则返回空数组)。不包括继承父类或接口的可重复注解。

  • Class.instance.getDeclaredAnnotations() - 获取 class 对象所表示的类上的全部注解(不包括继承父类和接口的注解)。

  • Class.instance.getAnnotatedSuperclass() - 获取 class 对象所表示的类的父类的注解。返回一个 AnnotatedType 对象,可通过该对象的 getAnnotations() 方法获取注解对象列表。获取的内容包括:

    • 父类声明处的类型注解,如 @Deprecated
    • 泛型参数上的类型注解,如 @NotNull
    • 完整的泛型类型信息
  • Class.instance.getAnnotatedInterfaces() - 获取 class 对象所表示的类所实现接口的注解。返回一个 AnnotatedType 数组(每个元素表示一个接口的注解信息),可通过元素对象的 getAnnotations() 方法获取注解对象列表。

小结

本文对 Class.java 做一点点浅层分析,对于刚入门的同学来说,这足以让大家理解很多框架(如 Spring)的思想了。掌握 Class 类是理解 Java 反射的前提。下这点功夫,是很值得的。