记得vamcily 曾问我:“为什么获取数组的长度用.length(成员变量的形式),而获取String的长度用.length()(成员方法的形式)?”

我当时一听,觉得问得很有道理。做同样一件事情,为什么采用两种风格迥异的风格呢?况且,Java中的数组其实是完备(full-fledged)的对象,直接暴露成员变量,可能不是一种很OO的风格。那么,设计Java的那帮天才为什么这么做呢?

带着这个疑问,我查阅了一些资料,主要是关于“JVM是如何处理数组”的。

数组对象的类是什么?

既然数组都是对象,那么数组的类究竟是什么呢?当然不是java.util.Arrays啦!我们以int一维数组为例,看看究竟。

[java] view plaincopy

 
  1. publicclass Main {
  2. publicstaticvoid main(String args[]){
  3. int a[] = newint[10];
  4. Class clazz = a.getClass();
  5. System.out.println(clazz.getName());
  6. }
  7. }

在SUN JDK 1.6上运行上述代码,输出为:

[I

看起来数组的类很奇怪,非但不属于任何包,而且名称还不是合法的标识符(identifier)。具体的命名规则[1]可以参见java.lang.Class.getName()的javadoc。简单的说,数组的类名由若干个'['和数组元素类型的内部名称组成,'['的数目代表了数组的维度。

具有相同类型元素和相同维度的数组,属于同一个类。如果两个数组的元素类型相同,但维度不同,那么它们也属于不同的类。如果两个数组的元素类型和维度均相同,但长度不同,那么它们还是属于同一个类。

数组的类有哪些成员呢?

既然我们知道了数组的类名是什么,那么就去看看数组的类究竟是什么样的吧?有哪些成员变量?有哪些成员方法?length这个成员变量在哪?是不是没有length()这个成员方法?

找来找去,在JDK的代码中没有找打'[I'这个类。想想也对,'[I'都不是一个合法的标识符,肯定不会出现public class [I {...}这样的Java代码。我们暂且不管[I类是谁声明的,怎么声明的,先用反射机制一探究竟吧。

[java] view plaincopy

 
  1. publicclass Main {
  2. publicstaticvoid main(String[] args) {
  3. int a[] = newint[10];
  4. Class clazz = a.getClass();
  5. System.out.println(clazz.getDeclaredFields().length);
  6. System.out.println(clazz.getDeclaredMethods().length);
  7. System.out.println(clazz.getDeclaredConstructors().length);
  8. System.out.println(clazz.getDeclaredAnnotations().length);
  9. System.out.println(clazz.getDeclaredClasses().length);
  10. System.out.println(clazz.getSuperclass());
  11. }
  12. }

在SUN JDK 1.6上运行上述代码,输出为:

0
0
0
0
0
class java.lang.Object

可见,[I这个类是java.lang.Object的直接子类,自身没有声明任何成员变量、成员方法、构造函数和Annotation,可以说,[I就是个空类。我们立马可以想到一个问题:怎么连length这个成员变量都没有呢?如果真的没有,编译器怎么不报语法错呢?想必编译器对Array.length进行了特殊处理哇!

数组的类在哪里声明的?

先不管为什么没有length成员变量,我们先搞清楚[I这个类是哪里声明的吧。既然[I都不是合法的标识符,那么这个类肯定在Java代码中显式声明的。想来想去,只能是JVM自己在运行时生成的了。JVM生成类还是一件很容易的事情,甚至无需生成字节码,直接在方法区中创建类型数据,就差不多完工了。

还没有实力去看JVM的源代码,于是翻了翻The JavaTM Virtual Machine Specification Second Edition,果然得到了验证,相关内容参考5.3.3
Creating Array Classes

规范的描述很严谨,还掺杂了定义类加载器和初始化类加载器的内容。先不管这些,简单概括一下:

  1. 类加载器先看看数组类是否已经被创建了。如果没有,那就说明需要创建数组类;如果有,那就无需创建了。
  2. 如果数组元素是引用类型,那么类加载器首先去加载数组元素的类。
  3. JVM根据元素类型和维度,创建相应的数组类。

呵呵,果然是JVM这家伙自个偷偷创建了[I类。JVM不把数组类放到任何包中,也不给他们起个合法的标识符名称,估计是为了避免和JDK、第三方及用户自定义的类发生冲突吧。

再想想,JVM也必须动态生成数组类,因为Java数组类的数量与元素类型、维度(最多255)有关,相当相当多了,是没法预先声明好的。

居然没有length这个成员变量!

我们已经发现,偷懒的JVM没有为数组类生成length这个成员变量,那么Array.length这样的语法如何通过编译,如何执行的呢?

让我们看看字节码吧!编写一段最简单的代码,使用jclasslib查看字节码。

[java] view
plain
copy

 
  1. publicclass Main {
  2. publicstaticvoid main(String[] args) {
  3. int a[] = newint[2];
  4. int i = a.length;
  5. }
  6. }

使用SUN JDK 1.6编译上述代码,并使用jclasslib打开Main.class文件,得到main方法的字节码:

0 iconst_2 //将int型常量2压入操作数栈
1 newarray 10 (int) //将2弹出操作数栈,作为长度,创建一个元素类型为int, 维度为1的数组,并将数组的引用压入操作数栈
3 astore_1 //将数组的引用从操作数栈中弹出,保存在索引为1的局部变量(即a)中
4 aload_1 //将索引为1的局部变量(即a)压入操作数栈
5 arraylength //从操作数栈弹出数组引用(即a),并获取其长度(JVM负责实现如何获取),并将长度压入操作数栈
6 istore_2 //将数组长度从操作数栈弹出,保存在索引为2的局部变量(即i)中
7 return //main方法返回

可见,在这段字节码中,根本就没有看见length这个成员变量,获取数组长度是由一条特定的指令arraylength实现(怎么实现就不管了,JVM总有办法)。编译器对Array.length这样的语法做了特殊处理,直接编译成了arraylength指令。另外,JVM创建数组类,应该就是由newarray这条指令触发的了。

很自然地想到,编译器也可以对Array.length()这样的语法做特殊处理,直接编译成arraylength指令。这样的话,我们就可以使用方法调用的风格获取数组的长度了,这样看起来貌似也更加OO一点。那为什么不使用Array.length()的语法呢?也许是开发Java的那帮天才对.length有所偏爱,或者抛硬币拍脑袋随便决定的吧。 形式不重要,重要的是我们明白了背后的机理。

Array in Java

最后,对Java中纯对象的数组发表点感想吧。

相比C/C++中的数组,Java数组在安全性要好很多。C/C++常遇到的缓存区溢出或数组访问越界的问题,在Java中不再存在。因为Java使用特定的指令访问数组的元素,这些指令都会对数组的长度进行检查。如果发现越界,就会抛出java.lang.ArrayIndexOutOfBoundsException。

Java数组元素的灵活性比较大。一个数组的元素本身也可以是数组,只要所有元素的数组类型相同即可。我们知道数组的类型和长度无关,因此元素可以是长度不同的数组。这样,Java的多维数组就不一定是规规矩矩的矩阵了,可以千变万化。

转自:(https://blog.csdn.net/personbing/article/details/51394286)

【转】为什么使用length获取Java数组的长度的更多相关文章

  1. 有关JVM处理Java数组方法的思考

    在Java中,获取数组的长度和String的长度是两种不同的方法,这引起了本文作者的一番思考.本文从JVM的角度,探讨了Java数组在JVM中是什么对象,有哪些成员,以及声明方法. 作者:jarfie ...

  2. 关于Java数组

    今天,我们将要谈到的是Java里的数组 数组是一种容器,它是一些相同类型元素的集合.我们可以用数组,去存储一些相同类型的数据,比如,整数,浮点数,字符,...事实上,数组甚至可以用来存储同一个类的多个 ...

  3. 第3章 Java数组(上): 一维数组和二维数组

    3.数组及排序算法(2天) 3.1 数组的概述 2课时 3.2 一维数组的使用 3课时 3.3 多维数组的使用 3课时 3.4 数组中涉及到的常见算法 3课时 3.5 Arrays工具类的使用 3课时 ...

  4. Java数组备忘录

    前言 近期用Java做ACM题目的时候,常常忘记数组怎样实现静态初始化,所以这里记录一下Java数组使用的常识. Java数组常识 数组在Java中是一个对象,数组实例须要通过new操作符进行创建. ...

  5. Java 数组(一)定义与访问

    一.数组 1.容器概述    容器:是将多个数据存储到一起,每个数据称为该容器的元素. 2.数组概述  数组:数组就是存储数据长度固定的容器,保证多个数据的数据类型要一致. 数组特点: (1)数组是一 ...

  6. Java 数组最佳指南,快收藏让它吃灰

    两年前,我甚至写过一篇文章,吐槽数组在 Java 中挺鸡肋的,因为有 List 谁用数组啊,现在想想那时候的自己好幼稚,好可笑.因为我只看到了表面现象,实际上呢,List 的内部仍然是通过数组实现的, ...

  7. Java数组操作利器:Arrays工具类

    java.util.Arrays提供大量的工具方法来操作数组,这些方法全是静态方法. 1 便捷创建List public static <T> List<T> asList(T ...

  8. JAVA 数组实例-求学生平均成绩,与计算数组的长度

    实例: 知识点:数组名.length是计算数组的长度 import java.util.*; //求学生平均分成绩 public class Test{ public static void main ...

  9. Java 数组元素逆序Reverse的三种方式

    Java 数组元素逆序Reverse的三种方式   本文链接:https://blog.csdn.net/xHibiki/article/details/82930521 题目 代码实现 说明 int ...

随机推荐

  1. python 利用selenium爬取百度文库的word文章

    今天学习如何使用selenium库来爬取百度文库里面的收费的word文档 from selenium import webdriver from selenium.webdriver.common.k ...

  2. Struts(六)

    JSON(JavaScript Object Notation)    1.一种轻量级的数据交换格式    2.通常用于在客户端和服务器之间传递数据    3.jQuery的所有参数都是以JSON格式 ...

  3. 【原创】在 .NET Core 3.1 中使用 Senparc.Weixin.Work 企业微信 SDK —— 发送文本消息

    下面在控制台应用里展示一个简单的例子来实现发送文本消息. 本文目录: 创建控制台应用 添加SDK引用 命令行方式 进入项目目录 添加包引用 配置和使用SDK 添加appsettings.json文件 ...

  4. linux笔记之解压

    从1.15版本开始tar就可以自动识别压缩的格式,故不需人为区分压缩格式就能正确解压: Linux下常见的压缩包格式有5种:zip tar.gz tar.bz2 tar.xz tar.Z 其中tar是 ...

  5. HDU_1175_A*

    http://acm.split.hdu.edu.cn/showproblem.php?pid=1043 刚开始一脸蒙逼,看了题解之后,参考了A*算法. 参考:http://www.cnblogs.c ...

  6. linux下手动安装/升级GCC到较高版本

    一.环境 VMWare+Centos7 二.写在前面的话 安装GCC最简单的方式当然是[yum -y install gcc]但是我的机器上安装下来后,其版本是4.8.5,感觉有点低,所以想升级一下( ...

  7. Newcoder Wannafly13 B Jxy军训(费马小定理、分数在模意义下的值)

    链接:https://www.nowcoder.com/acm/contest/80/B 题目描述 在文某路学车中学高一新生军训中,Jxc正站在太阳下站着军姿,对于这样的酷热的阳光,Jxc 表示非常不 ...

  8. TCP协议三次握手(通信)

    在<计算机网络>一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失 ...

  9. 转:RBAC如何设计一个权限系统

    前言 权限管理是所有后台系统的都会涉及的一个重要组成部分,主要目的是对不同的人访问资源进行权限的控制,避免因权限控制缺失或操作不当引发的风险问题,如操作错误,隐私数据泄露等问题.目前在公司负责权限这块 ...

  10. new 的实现原理

    自己封装一个new <script> // 创建一个构造函数 function Father() { this.name = '小红'; this.eat = function () { ...