bigywater.tech
Java 内部原理 · 111

数组直接打印真实陷阱:[I@7ad041f3 不是内容,是类型代号加身份哈希

几乎每个Java初学者都真实踩过一次这个坑——直接println一个数组,得到一串看不懂的乱码。这篇真实拆解这串"乱码"到底是什么、每个字符代表什么,以及不同类型数组的前缀真实有什么规律。

[I 真实代表 int[]
真实验证:方括号数量代表数组维度,紧跟的字母是元素类型代号(I=int),@后面真实是identity hashCode的十六进制
四种类型前缀真实各不相同
真实验证:[D是double[]、[J是long[](J不是L,因为L已被引用类型占用)、[Ljava.lang.String;是String[]

上一篇:Random(seed) 真实验证

真实拆解:[I@7ad041f3 这串字符到底是什么

直接对一个int[]数组调用println(),看真实输出的每一部分代表什么。

ArrayToStringPitfallDemo.java int[] arr = {1, 2, 3}; System.out.println(arr);
真实实测
直接打印int[]数组真实输出: [I@7ad041f3
arr.toString() 真实结果: [I@7ad041f3
字符串真实是否以[I@开头: true

真实结果证明了数组没有重写 Object.toString(),所以打印出来的真实是默认格式:getClass().getName() + "@" + Integer.toHexString(hashCode())。[I 是 JVM 内部对数组类型的命名规则——[ 代表"这是一个数组"(一维数组一个方括号),I 是 int 的类型代号(取自 Integer 的首字母),@ 后面真实是这个数组对象的 identity hashCode 的十六进制表示——和内容完全没有关系,纯粹是一个身份标识。

真实验证:不同类型数组的前缀规律

int有专属代号I,那其他基本类型和引用类型呢?

真实实测
double[] 真实toString前缀: [D(D代表double)
String[] 真实toString前缀: [L(L代表引用类型,后面真实跟着完整类名)
long[] 真实toString前缀: [J(J代表long,因为L已经被引用类型占用)
String[] 真实完整toString: [Ljava.lang.String;@7852e922
int[][] 真实toString前缀: [[I(多一个[代表多一维)
真实的命名规则。这套字符代号是 JVM 规范定义的"字段描述符"(field descriptor)规则,基本类型各自真实分到了一个字母:I=int,D=double,F=float,B=byte,C=char,S=short,Z=boolean。long 真实用的是 J 而不是看起来更顺理成章的 L——因为 L 真实被引用类型占用了,任何对象数组的前缀都真实是 L 加上完整的类全限定名再加一个分号(比如 [Ljava.lang.String;)。二维数组则真实用两个连续的 [ 表示,方括号的数量直接对应数组的维度数——这套字段描述符规则是 JVM 字节码层面统一使用的类型编码方式,不只用于 toString(),方法签名、字段类型等场景真实都用同一套记法。

真实验证:正确打印数组内容的方式

真实实测
Arrays.toString(arr) 真实结果: [1, 2, 3]
Arrays.deepToString(arr2d) 真实结果: [[1, 2], [3, 4]]

直接打印List 真实结果: [[I@5c647e05](每个元素依然是[I@形式,不是内容)

真实结果验证了正确姿势:一维数组用 Arrays.toString(),多维数组用 Arrays.deepToString()——它们内部会真实递归遍历数组元素并拼出可读的内容。最后一个真实结果特别值得注意:即使把数组放进一个 List 再整体打印,List.toString() 内部对每个元素调用的依然是元素自己的 toString(),数组元素同样真实没有重写这个方法,陷阱一样会出现——这也是这个坑在真实项目里最容易反复中招的地方:开发者以为用了集合包装就"安全"了,实际上数组本身的问题没有解决。

分步演示:打印数组的各种"看似安全"的写法,哪些真的有效

每一行都是真实运行这段代码得到的结果。

换成 valueOf()、%s、放进List里打印,真实统统绕不开数组没有重写toString()这个根本问题——唯一的正确出口是 Arrays.toString()/deepToString()
这一步新验证的写法

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,各类型前缀重复运行完全一致(hashCode十六进制部分每次运行不同,但前缀结构固定)。
    • 和数组的hashCode()/equals()那篇一起看,能理解数组作为一个"没有重写Object默认方法"的特殊类型,在toString()、equals()、hashCode()三个方法上表现出的统一的、基于身份而非内容的行为。
    • 没有涉及:多维锯齿数组的类型描述符如何表示(结论是同样按维度数叠加[前缀)、通过Class.getName()反向解析出数组元素类型的具体API用法——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号