public class Test {
public static void main(String[] args) {
int[] arr = new int[5];
System.out.println(arr.length);
System.out.println("Hello".length());
}
}
为什么获取数组的长度用 .length (成员变量的形式),而获取String的长度用 .length() (成员方法的形式)?”
In the Java programming language, arrays are objects (§4.3.1), are dynamically created, and may be assigned to variables of type Object (§4.3.2). All methods of class Object may be invoked on an array.
Every array has an associated Class object, shared with all other arrays with the same component type.
Although an array type is not a class, the Class object of every array acts as if:
The direct superclass of every array type is Object.
Every array type implements the interfaces Cloneable and java.io.Serializable.
数组在 Java 里是一种特殊类型,有别于普通的“类的实例”的对象。它是由 JVM 直接创建的,因此查看类名的时候会发现是很奇怪的类似于”[I”这样的样子,这个直接创建的对象的父类就是Object,所以可以调用 Object 中的所有方法。
每个数组都有一个相关联的 Class 对象,也就可以进行运行时的类型检查。
int[] arr = new int[5];
System.out.println(arr.getClass().getName());
System.out.println(arr.getClass().getSuperclass().getName());
输出:
[I
java.lang.Object
try {
Class> aClass = Class.forName("[I");
System.out.println(aClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
输出:
class [I
数组的类型由若干个’[‘和数组元素类型的内部名称组成。
‘[‘的数目代表了数组的维度。
具有相同类型元素和相同维度的数组,属于同一个相关联的类对象。
如果两个数组的元素类型相同,但维度不同,那么它们属于不同的相关联的类对象。
如果两个数组的元素类型和维度均相同,但长度不同,那么它们还是属于同一个相关联的类对象。
利用反射机制我们来观察下这个所对应的类:
int[] arr = new int[5];
Class extends int[]> clazz = arr.getClass();
System.out.println(clazz.getDeclaredFields().length);
System.out.println(clazz.getDeclaredMethods().length);
System.out.println(clazz.getDeclaredConstructors().length);
System.out.println(clazz.getDeclaredAnnotations().length);
System.out.println(clazz.getDeclaredClasses().length);
System.out.println(clazz.getSuperclass());
System.out.println(Arrays.toString(clazz.getInterfaces()));
在JDK 1.8上运行上述代码,输出为:
0
0
0
0
0
class java.lang.Object
[interface java.lang.Cloneable, interface java.io.Serializable]
可见,[I这个类是java.lang.Object的直接子类,自身没有声明任何成员变量、成员方法、构造函数和注解。
可以说,[I就是个空类。
我们立马可以想到一个问题:怎么连length这个成员变量都没有呢?如果真的没有,编译器怎么不报语法错呢?想必编译器对.length进行了特殊处理哇!
public class Test {
public static void main(String[] args) {
int[] arr = new int[5];
System.out.println(arr.length);
System.out.println("Hello".length());
}
}
生成字节码:
D:\N3verL4nd\Desktop>javac Test.java
D:\N3verL4nd\Desktop>javap -c Test
Compiled from "Test.java"
public class Test {
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5 //将int型常量 5 压入操作数栈
1: newarray int //将 5 弹出操作数栈,作为长度,创建一个元素类型为int, 维度为 1 的数组,并将数组的引用压入操作数栈
3: astore_1 //将数组的引用从操作数栈中弹出,保存在索引为1的局部变量(即arr)中
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_1 //将索引为 1 的局部变量(即arr)压入操作数栈
8: arraylength //从操作数栈弹出数组引用(即arr),并获取其长度(JVM负责实现如何获取),并将长度压入操作数栈
9: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String Hello 将常量池中的字符串常量"Hello"指向的堆中拘留String对象的地址压入操作数栈
17: invokevirtual #5 // Method java/lang/String.length:()I
20: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
23: return
}
可见,在这段字节码中,根本就没有看见length这个成员变量,获取数组长度是由一条特定的指令arraylength实现。
对于 HotSpot VM,在数组对象的对象头里有一个 _length 字段,记录数组长度。
arraylength 字节码的实现只要去读那个 _length 字段即可。