【发布时间】:2022-09-17 10:09:17
【问题描述】:
我经常看到使用 [L 然后是表示数组的类型的消息,例如:
[Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
(以上是我刚刚提取的任意示例。)我知道这表示一个数组,但是语法从何而来?为什么以[开头但没有右方括号?为什么是L?它纯粹是任意的还是背后有其他一些历史/技术原因?
【问题讨论】:
-
真的没有理由在人类读者的消息中使用这种格式。
我经常看到使用 [L 然后是表示数组的类型的消息,例如:
[Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
(以上是我刚刚提取的任意示例。)我知道这表示一个数组,但是语法从何而来?为什么以[开头但没有右方括号?为什么是L?它纯粹是任意的还是背后有其他一些历史/技术原因?
【问题讨论】:
[代表数组,Lsome.type.Here;代表数组的类型。这类似于使用的类型描述符在字节码内部见于§4.3 of the Java Virtual Machine Specification——。唯一的区别在于真实的描述符使用 / 而不是 . 来表示包。
例如,对于基元,值为:[I 对于整数数组,二维数组为:[[I(严格来说 Java 没有真正的二维数组,但您可以创建包含以下内容的数组数组)。
由于类可能有任何名称,因此很难确定它是什么类,因此它们以 L 分隔,后跟类名并以 ; 结束
描述符还用于表示字段和方法的类型。
例如:
(IDLjava/lang/Thread;)Ljava/lang/Object;
...对应一个参数为int、double、Thread,返回类型为Object的方法
编辑
您还可以使用 java 反汇编程序在 .class 文件中看到这一点
C:>more > S.java
class S {
Object hello(int i, double d, long j, Thread t ) {
return new Object();
}
}
^C
C:>javac S.java
C:>javap -verbose S
class S extends java.lang.Object
SourceFile: "S.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #2.#12; // java/lang/Object."<init>":()V
const #2 = class #13; // java/lang/Object
const #3 = class #14; // S
const #4 = Asciz <init>;
const #5 = Asciz ()V;
const #6 = Asciz Code;
const #7 = Asciz LineNumberTable;
const #8 = Asciz hello;
const #9 = Asciz (IDJLjava/lang/Thread;)Ljava/lang/Object;;
const #10 = Asciz SourceFile;
const #11 = Asciz S.java;
const #12 = NameAndType #4:#5;// "<init>":()V
const #13 = Asciz java/lang/Object;
const #14 = Asciz S;
{
S();
Code:
Stack=1, Locals=1, Args_size=1
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
java.lang.Object hello(int, double, long, java.lang.Thread);
Code:
Stack=2, Locals=7, Args_size=5
0: new #2; //class java/lang/Object
3: dup
4: invokespecial #1; //Method java/lang/Object."<init>":()V
7: areturn
LineNumberTable:
line 3: 0
}
在原始类文件中(查看第 5 行):
【讨论】:
[[I 仅表示 array-of-array-of-int。
JVM 数组描述符。
[Z = boolean
[B = byte
[S = short
[I = int
[J = long
[F = float
[D = double
[C = char
[L = any non-primitives(Object)
要获取主要数据类型,您需要:
[Object].getClass().getComponentType();
如果“对象”不是数组,它将返回 null。 要确定它是否是一个数组,只需调用:
[Any Object].getClass().isArray()
或者
Class.class.isArray();
【讨论】:
这在 JNI(通常在 JVM 内部)中用于指示类型。基元用单个字母表示(Z 表示布尔值,I 表示整数等),[ 表示数组,L 表示类(以 ; 结尾)。
看这里:JNI Types
编辑:详细说明为什么没有终止 ] - 此代码允许 JNI/JVM 快速识别方法及其签名。它旨在尽可能紧凑以加快解析速度(=尽可能少的字符),因此 [ 用于非常简单的数组(使用什么更好的符号?)。 I 对于 int 同样明显。
【讨论】:
[L 数组符号 - 它来自哪里?
来自 JVM 规范。这是在 classFile 格式和其他地方指定的类型名称的表示。
- “[”表示数组。事实上,数组类型名称是
[<typename>,其中<typename>是数组基类型的名称。- 'L'实际上是基本类型名称的一部分;例如字符串是
"Ljava.lang.String;"。注意尾随的';'!!是的,该符号也记录在其他地方。
为什么?
毫无疑问,选择内部类型名称表示是因为它是:
- 紧凑,
- 自定界(这对于方法签名的表示很重要,这就是“L”和尾随“;”的原因),以及
- 使用可打印字符(为了易读性……如果不是可读性)。
但它是不清楚为什么他们决定通过
Class.getName()方法公开数组类型的内部类型名称。我觉得他们可以已将内部名称映射到更“人性化”的名称。我最好的猜测是,这只是他们直到为时已晚才开始解决的问题之一。 (没有人是完美的......即使是假设的“智能设计师”也是如此。)
【讨论】:
我认为这是因为 C 被 char 占用了,所以类中的下一个字母是 L。
【讨论】:
另一个来源是Class.getName() 的文档。当然,所有这些规格都是一致的,因为它们是相互配合的。
【讨论】: