网站首页 > 精选教程 正文
在上一篇文章中提到过:在JVM级别,一个构造函数就是一个名称为<init>(名称是由编译器提供的)的方法,这个特殊的方法就是类实例的初始化方法。如果一个类有多个构造函数,那么就会有多个对应的实例初始化方法。当一个类实例被创建,类变量已经被初始化了默认值,实例初始化方法就会被调用。
一个类实例被创建的过程中都发生了什么?数值数组、引用类型数组、一位数组、多维数组是怎样创建的?又是如何使用和操作的?这一章节详细描述类实例以及对象的创建和使用过程。
实例对象的创建和使用
类实例的创建和传递
JVM使用new指令创建类实例,直接上例子:
Object create(){
return new Object();
}
编译后:
Method java.lang.Object create()
0 new #1 //Class java.lang.Object 创建一个Object类的实例并将引用压入操作数栈。#1指向当前类运行时常量池中的一个Object类型引用
3 dup //将栈顶的类实例引用复制为两个(后续指令要用到两次实例的引用)
4 invokespecial #4 // Method java.lang.Object.<init>()V。从操作数栈弹出一个引用作为this传入Object类的类实例初始化方法,并执行初始化
7 areturn //返回栈顶的类实例引用
类实例作为引用类型被传递和返回,这非常类似于数值类型。
int i; // 一个实例变量
MyObj example(){
MyObj o=new MyObj();
return silly(o);
}
MyObj silly(MyObj o){
if (o!=null){
return o;
}else{
return o;
}
}
编译后:
Method MyObj example()
0 new #2 // Class MyObj 创建一个MyObj类的实例并将引用压入操作数栈。#1指向当前类运行时常量池中的一个MyObj类型引用
3 dup //将栈顶的类实例引用复制为两个(后续指令要用到两次实例的引用)
4 invokespecial #5 // Method MyObj.<init>()V 从操作数栈弹出一个引用作为this传入MyObj类的类实例初始化方法,并执行初始化
7 astore_1 //弹出栈顶的类实例引用,存到局部变量表索引为1的位置,即变量o
8 aload_0 //将局部变量表索引0处的值(即example方法调用者类实例的引用)压入操作数栈
9 aload_1 //将局部变量表索引1出的值(即MyObj类实例的引用)压入操作数栈
10 invokespecial #4 //调用silly方法,并将结果压入操作数栈。#4指向当前类运行时常量池中的方法引用,指向silly方法
13 areturn //返回栈顶的对象引用
Method MyObj silly(MyObj)
0 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)压入操作数栈
1 ifnull 6 //如果栈顶对象是null,则控制流转到指令数组索引6处的指令,否则继续执行后续指令
4 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)重新压入操作数栈
5 areturn //从栈顶弹出MyObj类实例的引用,压入到调用者栈帧的操作数栈,程序结束。
6 aload_1 //将局部变量表索引为1的值(即传入的MyObj类实例引用)压入操作数栈
7 areturn //从栈顶弹出MyObj类实例的引用,压入到调用者栈帧的操作数栈,程序结束(此时返回的引用是null)。
类实例字段的访问
JVM使用getfield和putfield指令访问类实例的字段(实例变量)。假设i是一个int类型的实例变量,那么setIt()和getIt()方法定义如下:
void setIt(int value){
i=value;
}
int getIt(){
return i;
}
编译后:
Method void setIt(int)
0 aload_0 //将局部变量表索引为0的值(一个类实例引用)压入操作数栈【准备类实例引用】
1 iload_1 //将局部变量表索引为1的值(一个整型数值)压入操作数栈【准备字段值】
2 putfield #4 //Field Example.i I //将字段值赋给类实例引用的指定字段,即i字段。#4是类实例字段的符号引用
5 return //结束返回
Method int getIt()
0 aload_0 //将局部变量表索引为0的值(一个类实例引用)压入操作数栈
1 getfield #4 //Field Example.i I //获取类实例的指定字段值(i的值),压入操作数栈
4 ireturn //从操作数栈弹出整型数值并返回
与方法调用指令的操作数一样,putfield和getfield指令的操作数(运行时常量池索引#4)不是类实例中字段的偏移量。编译器生成对实例字段的符号引用,这些字段存储在运行时常量池中。这些运行时常量池项在运行时被解析,以确定字段的实际位置。
数组的创建和使用
JVM数组也是对象,有专门的指令集用于数组的创建和操作。
数值类型数组
newarray指令用于创建数值类型的数组。
void createBuffer() {
int buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}
编译后:
Method void createBuffer()
0 bipush 100 //将整型常量100压入操作数栈
2 istore_2 //从操作数栈弹出整型常量100,放入局部变量表索引2的位置
3 bipush 12 //将整型常量12压入操作数栈
4 istore_3 //从操作数栈弹出整型常量12,放入局部变量表索引3的位置
6 iload_2 //将局部变量2(整型常量100)压入操作数栈
7 newarray int //从操作数栈中弹出100作为数组长度,创建一个int类型数组,并将引用压入操作数栈
9 astore_1 //将数组引用弹出栈,放到局部变量表索引1的位置,即buffer变量的位置,buffer指向了新创建的数组对象
10 aload_1 //将局部变量1中的数组引用压入操作数栈 【准备引用】
11 bipush 10 //将整型常量10压入操作数栈 【准备索引】
13 iload_3 //将局部变量3(整型值12)压入操作数栈 【准备数值】
14 iastore //在数组索引为10的位置,存储整型值12
15 aload_1 //再将数组引用压入操作数栈【准备引用】
16 bipush 11 //将整型常量11压入操作数栈 【准备索引】
18 iaload //将数组中索引为11的数值压入操作数栈,即buffer[11]
19 istore_3 //从操作数栈弹出buffer[11]的值,作为整数存储到局部变量3,即value变量。
在本次对createBuffer方法的调用中,栈帧局部变量表中的变量依次是:this、buffer、bufsz、value,对应的索引是0、1、2、3。如果你已经理解了实例方法如何被调用,就不会对此处有疑惑,否则建议先回顾下之前讲方法调用的章节。
引用类型数组
anewarray指令用于创建对象引用的一维数组,例如:
void createThreadArray() {
Thread threads[];
int count = 10;
threads = new Thread[count];
threads[0] = new Thread();
}
编译后:
Method void createThreadArray()
0 bipush 10 //将整型常量10压入操作数栈
2 istore_2 //从操作数栈弹出整型常量10,放入局部变量表索引2的位置,即count变量
3 iload_2 //将count变量值(10)压入操作数栈
4 anewarray class #1 //创建一个Thread类型的数组,长度为10,将数组引用压入操作数栈
7 astore_1 //从操作数栈弹出数组引用,放入局部变量表索引1的位置,即threads变量,threads指向了新创建的数组对象
8 aload_1 //将thread变量中的数组引用压入操作数栈【准备数组引用】
9 iconst_0 //将整型常量0压入操作数栈【准备数组索引】
10 new #1 //创建一个Thread类实例,引用压入操作数栈【准备数组元素值】
13 dup //复制栈顶Thread实例的引用,作为this参数提供给invokespecial指令使用。(此时操作数栈栈顶有两个相同的Thread实例引用,指向同一个对象)
14 invokespecial #5 //调用Thread类实的构造函数,即类实例初始化方法,弹出栈顶的Thread实例引用,传入java.lang.Thread.<init>V并执行函数。
17 aastore //将Thread实例的引用存入数组索引0的位置
18 return
多维数组
anewarray指令还可以用于创建多维数组中的一个维度,multianewarray则可以用来一次性创建多个维度,比如,三维矩阵:
int[][][] create3DArray() {
int grid[][][];
grid = new int[10][5][];
return grid;
}
编译后:
Method int create3DArray()[][][]
0 bipush 10 //将整型常量10压入操作数栈(第一个维度的长度)
2 iconst_5 //将整型常量5压入操作数栈(第二个维度的长度)
3 multianewarray #1,2 //创建数组并压入操作数栈。#1指向了运行时常量池中的一个类型:[[[I。从操作数栈中获取前两个维度的数组长度,分别是10和5。
7 astore_1 //从操作数栈中弹出数组的引用,放入局部变量表索引1的位置,即变量grid
8 aload_1 //将局部变量1中的数组索引压入操作数栈
9 areturn //从操作数栈中弹出数组索引,返回到调用者栈帧的操作数栈中
multianewarry指令的第一个操作数是一个运行时常量池的索引,指向还要创建的数组的类型,第二个参数代表要创建的数组的维度。因为代码中只指定了前两个维度的数组长度,第三个维度并没有指定长度,因此jvm实际只会创建前两个维度的数据,并从堆内存中分配空间。当我们把new int[10][5][]替换为new int[10][5][2],编译后代码就会变为multianewarry #1,3。
多维数组也是一个对象,分别由aload_1和areturn指令进行加载压栈和返回。所有数组都有自己的长度,可以通过arraylength指令访问。
猜你喜欢
- 2024-11-16 来,一起聊聊Excel中的数组(excel数组公式有哪些)
- 2024-11-16 数组的维数及数组公式的讲解(数组维的大小)
- 2024-11-16 C语言中数组的类型和使用(c语言中数组的概念及作用)
- 2024-11-16 C# - 多维与交错数组 036(交错数组和多维数组有何区别?)
- 2024-11-16 Numpy第6练:如何操控数组的维度与形状?干货满满!
- 2024-11-16 NumPy之:多维数组中的线性代数(numpy 多维数组)
- 2024-11-16 S7-1200 数组数据类型(ARRAY)(s71200plc数据类型)
- 2024-11-16 JAVA中如何调用matlab并返回运算结果?
- 2024-11-16 Java基础03(java基础案例教程第二版)
- 2024-11-16 数据结构:数组的操作(数据结构数组的顺序存储)
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)