第15章 对象和数组

只有对象引用和基本类型可以在Java的桟中以局部变量形式存在。Java栈不能容纳对象。

Java虚拟机中的对象和基本类型的结构分离在java编程语言中体现为:对象不能作为局部变量声明,只有对象引用和基本类型可以。对象引用在声明后并不指向任何有意义的东西,只有在引用被显式初始化后(无论是使引用指向一个已存在的对象还是新建一个对象)对象引用才会指向一个真实的对象。

在Java虚拟机指令集中,除了数组以外,所有的对象都使用同样的操作码来实例化和存取。 如前所述,Java中的数组是完善(ftill-fledged )的对象。和Java中其他对象一样,数组是动态创建的,数组引用可以在任何需要用到引用来标识对象的地方使用,数组中对象的任何方法都可以被调用。但Java虚拟机中仍然使用特殊的字节码来处理数组。

如同其他的对象,数组不能作为局部变量来使用,只有数组引用才可以。数组对象本身通 常包括基本类型数组或者对象引用数组。如果声明了对象数组,获得的将是对象引用的数组。 对象本身必须通过new操作显式创建,并且陚给数组成员。

15.2针对对象的操作码

实例化一个新对象需要通过new操作码来实现,如表15-1所示。new操作码后面紧随着两个字长的操作数,这两个字长的操作数合起来表示常量池中的一个不带符号的16位长度的索引。 在特定偏移量位置处的常量池人口给出了新对象所属类的信息。如果还没有这些信息,那么虚拟机会解析这个常量池人口。它会为这个堆中的对象建立一个新的实例,用默认初始值初始化对象实例变量,然后把新对象的引用压人栈。

把对象的字段弹出、压人栈的操作码如表15—2所示。putfield和getfield这两个操作码只在字段是实例变量的情况下才执行。putstatic和getstatic对静态变量进行存取操作(这将在后面讨论)。 putfield和getfield指令都有两个操作数,这两个操作数合起来表示常量池中的不带符号的16位长度的索引。这个索引所指向的常量池入口包含了该宇段的所属类、名字和类型等信息。如果还没有这些信息,虚拟机会解析这个常量池人口。putfield和getfield对对象引用进行栈操作。 putfield指令从栈中取出实例变量值,getfield指令把获得的实例变量值压人栈。

下面所要介绍的操作码用来检查栈顶的对象引用是指向—个类的实例,还是指向以紧随操作码的操作数为索引的接口。在这两种情况下,虚拟机都会产生指向常量池人口的无符号16位长度索引,然后把它的值賦给操作码后的两个字节。如果还没有这些内容,虚拟机会解析常量池入口。

如果所给的对象不是指定类或者接口的实例,checkcast指令则会抛出一个CheckCast-Exception异常。否则,任何事情都不会发生。对象引用仍在栈中,下一条指令会接着执行。这条指令确保运行时类型转换的安全,并且是Java虚拟机安全框架的组成部分。

如表15-4所示,instanceof指令从栈顶端弹出对象引用,然后压人1或者0。如果对象确实是指定的类或者接口的实例,就向栈中压入1;反之,就向栈中压入0。instanceof指令用来实现java语言中的instanceof关键字,这个关键字用来测试一个对象是否为一个指定类或者接口的实例。


15.3针对数组的操作码
如表15-6所示,实例化新数组的工作可以通过newarray、anewarray和multianewarray操作码来完成。newarray操作码用来创建基本类型的数组,而不是对象引用的数组。基本类型由紧随newarray操作码的单字节操作数“atype"指定。newarray指令能够创建byte、short, char, int、 long、float、double或者boolean类型的数组。

需要注意的是,当数组类型显式声明为boolean时,Java虚拟机中创建数组的指令会以位为单位进行操作。此功能使实现,特别是需要控制内存需求的时候,可以采用位映像模式来压缩boolean数组。在这种表示方法中,一个数组的每个boolean元素都可以使用1位来表示。当内存不是很紧张的时候,boolean数组可以使用byte数组的方式实现,尽管这样会消耗掉更多的内存。无论虚拟机对于boolean数组使用哪一种内部实现,都会使用存取byte数组元素的操作码访问boolean数组的元素。这些操作码将在本章后面讨论。

anewarray指令用来建立一个对象引用的数组。anewarray指令有两个单字节长的操作数,它们紧随anewarray操作码之后,这两个操作数合起来表示常量池中的一个不带符号的16位长度的索引。创建的数组所针对的对象,其所属类的描述可以通过这个索引在常量池中找到。如果还 没有相关描述,虚拟机将会解析常量池入口。这条指令为对象引用数组分配空间,并把引用值初始化为null。

multianewarray指令用来分配多维数组,所谓多维数组,也就是数组的数组。而多维数组也 可以通过重复使用anewarray和newarray来进行分配。multianewarray指令只不过把创建多维数组 所需要的字节码压缩到一条指令中。multianewarray指令有两个紧随multianewarray操作码之后 的单字节长的操作数,这两个操作数合起来表示常量池中的不带符号的16位长度的索引。创建 的数组所针对的对象,其所属类的描述可以通过这个索引在常量池中找到。如果还没有相关描 述,虚拟机将会解析这个符号引用。紧随这两个操作数之后,是一个无符号的单字节操作数, 这个操作数用来表示多维数组的维数。每一维的长度都会从栈中弹出。这条指令为所有组成多维数组的数组分配空间。

multianewarray指令使用的常量池人口包含带有数组类名的CONSTANT_Class的项。例如, 一个四维float类型数组的常量池入口将会有一个形如“[[[[F”的名宇。常量池入口中的类名可以拥有比维数字节指定的更多(但不会更少)的左括号。虚拟机通常根据维数字节创建数组维数。

arraylength指令如表15-7所示。arraylength从栈顶端弹出一个数组引用,然后把这个数组的长度压人栈。

如表15-8所示的操作码从数组屮获取一个元素。虚拟机从栈中弹出数组的索引和数组引用, 再将位于给定数组的指定索引位置的值压入栈。baload操作码把byte或者boolean类型的值通过符号扩展转换为int类型,然后把这个int类型的值压入栈。同样,saload操作码把short类型的值通过符号扩展转换为int类型,然后再把这个int类型的值压人栈。caload把char类型的值通过零扩展转换为int类型的值,然后再把这个int类型的值压人栈。

initAnArray ()方法只对一个三维数组进行分配和初始化操作。该模拟演示了Java虚拟机处理三维数组的过程。为了对请求分配三维数组的指令multianewarray做出响应,Java虚拟机创建了一个树状的一维数组。由multianewarray指令返回的引用指向基础一维数组在initAnArray()方法中,基础数组有5个元素:threeD[0]到threeD[4]。基础数组的每个元素自身又是另外一个包含4个元素的一维数组的引用,这些二级数组可以通过threeD[0][0][0]到threeD[4][3][2]来访问。而这20个数组元素也都是数组引用,每一个都包含3个元素。这些元素都是int类型,同时,它们也都是这个三维数组的元素,它们都可以通过threeD[0][0][0]到threeD[4][3][2]来访问。

为了对initAnArray ()方法中的multianewarray指令做出响应,Java虚拟机创建了一个拥有5个数组元素的数组,5个拥有4个数组元素的数组,20个拥有3个int类型元素的数组。Java虚拟机为这26个数组在堆中分配空间,初始化它们的元素(让所有的元素形成了一棵树),然后返回基础数组的引用。

为了给此三维数组的元素赋一个int类型的值,Java虚拟机使用aaload来获取基础数组的一个元素,然后java虚拟机再对此元素(它本身是一个数组的数组)使用aaload指令来获取分支数组的一个子元素,这个子元素是一个指向第3级int类型数组的引用。最后,Java虚拟机使用iastore指令把一个int类型的值陚给这个第3级数组的元素。Java虚拟机通过对多个一维数组的访问来完成对多维数组的操作。

猜你喜欢

转载自www.cnblogs.com/mongotea/p/11980079.html