当前位置:首页 > 文章列表 > 文章 > java教程 > Java ClassDesc 怎样把类名转换为常量描述符

Java ClassDesc 怎样把类名转换为常量描述符

来源:17golang原创 2026-11-13 17:07:13 0浏览 收藏

需要把 Java 类名交给常量 API 时,优先使用 ClassDesc.of("java.lang.String");已经拿到 JVM 字段描述符时使用 ClassDesc.ofDescriptor("Ljava/lang/String;")。数组和基本类型也属于 ClassDesc 能表达的类型,但它们应该使用描述符或 arrayType 构造,不能把 int、String[] 当作普通类名传给 of。

官方地址:https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/lang/constant/ClassDesc.html

ClassDesc 记录的是“类型的名义描述”,不是已经加载出来的 Class>。先用正确的输入格式构造描述符,再按需要读取描述符字符串、拼装数组,最后才调用 resolveConstantDesc 做显式解析。

先分清类名、内部名和字段描述符

同一个 java.lang.String 类型,在不同 API 入口中有三种常见写法。把输入格式混用,是 ClassDesc 最常见的报错来源。

表示示例适合的入口
点号分隔的二进制类名java.lang.StringClassDesc.of
斜杠分隔的内部名java/lang/StringClassDesc.ofInternalName
JVM 字段描述符Ljava/lang/String;、[IClassDesc.ofDescriptor

ClassDesc 是 Java 12 引入的名义描述符接口。它可以表示类、接口、数组、基本类型以及 void,但“能表示”不等于“能被当前类加载器立即解析”。这两个阶段最好分开处理。

最小配方:用 ClassDesc.of 创建普通类描述符

普通类或接口使用点号分隔的完整二进制类名。嵌套类也可以直接使用二进制名称,或者先拿到外层描述符再调用 nested。

import java.lang.constant.ClassDesc;

public class ClassDescNameExample {
    public static void main(String[] args) {
        // 普通类名使用点号分隔;这里创建的是描述符,不会立刻加载 String。
        ClassDesc stringDesc = ClassDesc.of("java.lang.String");

        // 内部名使用斜杠分隔,适合已经来自 class 文件或字节码工具的输入。
        ClassDesc mapDesc = ClassDesc.ofInternalName("java/util/Map");

        // 通过 nested 表达嵌套类型,避免手写 '$' 和完整内部名。
        ClassDesc entryDesc = ClassDesc.of("java.util.Map").nested("Entry");

        System.out.println(stringDesc.descriptorString());
        System.out.println(mapDesc.displayName());
        System.out.println(entryDesc.descriptorString());
    }
}

of 接收的是完整的点号分隔二进制类名,ofInternalName 接收的是斜杠分隔的内部名。两者都只创建类型描述;如果名称格式不符合规则,会抛出 IllegalArgumentException。

Java ClassDesc 三种名称输入与类描述符结果之间的静态关系说明图
图1:点号类名、斜杠内部名与 ClassDesc 结果的静态关系图,不是运行截图或执行证据。

ofDescriptor 处理基本类型与数组

当输入已经是 JVM 字段描述符时,使用 ofDescriptor。对象类型以 L 开头、以分号结束;数组在组件描述符前加 [;基本类型使用单字母编码。

import java.lang.constant.ClassDesc;

public class ClassDescDescriptorExample {
    public static void main(String[] args) {
        // I 表示 int;V 表示 void,二者都属于常量 API 的描述范围。
        ClassDesc intDesc = ClassDesc.ofDescriptor("I");
        ClassDesc voidDesc = ClassDesc.ofDescriptor("V");

        // [I 表示 int[],[Ljava/lang/String; 表示 String[]。
        ClassDesc intArrayDesc = ClassDesc.ofDescriptor("[I");
        ClassDesc stringArrayDesc = ClassDesc.ofDescriptor("[Ljava/lang/String;");

        System.out.println(intDesc.displayName());
        System.out.println(voidDesc.displayName());
        System.out.println(intArrayDesc.displayName());
        System.out.println(stringArrayDesc.descriptorString());
    }
}

描述符的关键不是 Java 源码中的写法,而是 JVM 的字段描述符规则:I 是 int,J 是 long,Z 是 boolean,L...; 表示类或接口,[ 表示数组维度。未知字符、缺少分号或对象名格式错误都会在构造时被拒绝。

数组优先用 arrayType 组合,少手写方括号

如果程序已经有组件类型的 ClassDesc,建议使用 arrayType() 或 arrayType(rank)。这样数组维度由 API 组合,正文逻辑不需要自己拼接描述符字符串。

import java.lang.constant.ClassDesc;

public class ClassDescArrayExample {
    public static void main(String[] args) {
        // 先创建组件类型,再组合成一维和二维数组描述符。
        ClassDesc stringDesc = ClassDesc.of("java.lang.String");
        ClassDesc oneDimension = stringDesc.arrayType();
        ClassDesc twoDimensions = stringDesc.arrayType(2);

        // componentType 只对数组描述符有意义,普通类会返回 null。
        ClassDesc component = twoDimensions.componentType();

        System.out.println(oneDimension.descriptorString());
        System.out.println(twoDimensions.displayName());
        System.out.println(component.descriptorString());
    }
}

arrayType(2) 表示二维数组,不是数组元素数量。JVM 数组维度上限是 255,超过这个限制会抛出 IllegalArgumentException 或 IllegalStateException,因此不要用循环无限追加维度。

Java ClassDesc 的基本类型、数组维度与 componentType 读取关系说明图
图2:基本类型、数组组合和 componentType 读取关系的静态结构图,不是运行截图或执行证据。

用结果方法判断描述符,而不是猜字符串

构造完成后,优先使用类型方法判断结构:isPrimitive、isArray、isClassOrInterface 和 componentType 可以避免业务代码只比较字符串。需要写入日志或交给字节码 API 时,再读取 descriptorString;需要面向用户展示时,使用 displayName。

import java.lang.constant.ClassDesc;

public class ClassDescInspection {
    static void printShape(ClassDesc desc) {
        // 三个判断互斥地说明当前描述符属于哪一类,便于后续选择处理分支。
        String kind = desc.isPrimitive() ? "基本类型"
                : desc.isArray() ? "数组"
                : desc.isClassOrInterface() ? "类或接口" : "其他";

        System.out.println(kind + " / " + desc.displayName());
        System.out.println("descriptor=" + desc.descriptorString());

        // 非数组没有组件类型,不能直接调用 componentType 的结果方法。
        ClassDesc component = desc.componentType();
        if (component != null) {
            System.out.println("component=" + component.displayName());
        }
    }

    public static void main(String[] args) {
        printShape(ClassDesc.ofDescriptor("[[I"));
        printShape(ClassDesc.of("java.util.List"));
    }
}

需要 Class> 时再显式解析

ClassDesc 适合在常量描述、类文件 API 或延迟解析场景中传递。只有确实需要反射对象时,才调用 resolveConstantDesc,并明确提供 MethodHandles.Lookup 作为名称解析和访问控制上下文。

import java.lang.constant.ClassDesc;
import java.lang.invoke.MethodHandles;

public class ClassDescResolveExample {
    public static void main(String[] args) {
        ClassDesc desc = ClassDesc.of("java.lang.String");

        try {
            // lookup 决定解析时使用的访问上下文;解析失败属于反射异常。
            Class> resolved = desc.resolveConstantDesc(MethodHandles.lookup());
            System.out.println(resolved.getName());
        } catch (ReflectiveOperationException ex) {
            // 不把无法解析误认为构造失败,保留原异常用于上层处理或日志记录。
            System.err.println("无法解析 ClassDesc:" + ex.getMessage());
        }
    }
}

构造一个描述符不代表目标类一定存在,也不代表当前 Lookup 一定有访问权限。把“描述符格式错误”和“解析阶段找不到类或没有权限”分成两类处理,排查会更直接。

完整选择清单

  • 已知点号分隔的完整类名:使用 ClassDesc.of(name)。
  • 已知斜杠分隔的 class 文件内部名:使用 ClassDesc.ofInternalName(name)。
  • 输入来自字段描述符、基本类型或数组:使用 ClassDesc.ofDescriptor(descriptor)。
  • 已经有组件描述符:使用 arrayType() 或 arrayType(rank),不要手写多维数组前缀。
  • 只想检查结构:使用 isArray、isPrimitive、componentType 等 API。
  • 只有在需要反射对象时才调用 resolveConstantDesc,并处理 ReflectiveOperationException。

一句话记忆:类名交给 of,内部名交给 ofInternalName,JVM 描述符交给 ofDescriptor;数组从组件描述符组合,解析则留到真正需要 Class> 的边界。

相关问题

ClassDesc.of("int") 能表示基本类型吗?不能把 int 当作普通类名使用。基本类型应使用 ofDescriptor("I") 或 ConstantDescs.CD_int。

ClassDesc.of("java.lang.String[]") 能创建数组吗?不能。数组使用 ofDescriptor("[Ljava/lang/String;") 或先创建 String 描述符,再调用 arrayType()。

什么时候应该保存 descriptorString()?需要与 class 文件、字节码或其他 JVM 描述符 API 交互时保存;面向人阅读的日志通常使用 displayName() 更直观。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Java String.formatted 处理占位符与参数类型的实现方式Java String.formatted 处理占位符与参数类型的实现方式
上一篇
Java String.formatted 处理占位符与参数类型的实现方式
Java sealed interface 配合 switch 覆盖类型分支的方法
下一篇
Java sealed interface 配合 switch 覆盖类型分支的方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    1014次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    1018次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    1142次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    1116次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    959次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码