我的后端学习大纲
JVM学习大纲
4.类型转换指令:
类型转换指令说明
①类型转换指令可以将两种不同的数值类型进行相互转换。
这些转换操作一般用于实现用户代码中的显式类型转换操作,或者用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
4.1.宽化类型转换:
宽化类型转换(widening Numeric Conversions)
a.转换规则:
Java虚拟机直接支持以下数值的宽化类型转换(widening numeric conversion,小范围类型向大范围类型的安全转换)。也就是说,并不需要指令执行,包括:
从int类型到long、float或者double类型。对应的指令为: i2l、i2f、i2d
从long类型到float、double类型。对应的指令为:l2f、l2d
从float类型到double类型。对应的指令为:f2d
简化为: int --> long -->float --> double
b.精度损失问题:
宽化类型转换是不会因为超过目标类型最大值而丢失信息的,例如,从int转换到 long,或者从int转换到double,都不会丢失任何信息,转换前后的值是精确相等的。
从int、long类型数值转换到float,或者long类型数值转换到double时,将可能发生精度丢失—―可能丢失掉几个最低有效位上的值,转换后的浮点数值是根据IEEE754最接近舍入模式所得到的正确整数值。
尽管宽化类型转换实际上是可能发生精度丢失的,但是这种转换永远不会导致Java虚拟机抛出运行时异常。
//举例:精度损失的问题
@Test
public void upCast2(){int i = 123123123;float f = i;System.out.println(f);//123123120long l = 123123123123L;l = 123123123123123123L;double d = l;System.out.println(d);//123123123123123120
}
c.补充说明:
从byte、char和short类型到int类型的宽化类型转换实际上是不存在的。对于byte类型转为int ,虚拟机并没有做实质性的转化处理,只是简单地通过操作数栈交换了两个数据。而将byte转为long时,使用的是i2l,可以看到在内部byte在这里已经等同于int类型处理,类似的还有short类型,这种处理方式有两个特点:
一方面可以减少实际的数据类型,如果为short和byte都准备一套指令,那么指令的数量就会大增,而虚拟机目前的设计上,只愿意使用一个字节表示指令,因此指令总数不能超过256个,为了节省指令资源,将short和byte当做int处理也在情理之中。
另一方面,由于局部变量表中的槽位固定为32位,无论是byte或者short存入局部变量表,都会占用32位空间。从这个角度说,也没有必要特意区分这几种数据类型。
4.2.宽化类型转换:
窄化类型转换(Narrowing Numeric Conversion)
a.转换规则:
Java虚拟机也直接支持以下窄化类型转换:
从int类型至byte、short或者char类型。对应的指令有: i2b、 i2c、i2s
从long类型到int类型。对应的指令有:l2i
从float类型到int或者long类型。对应的指令有:f2i、f2l
从double类型到int、long或者float类型。对应的指令有: d2i、d21、d2f
b.精度损失问题
窄化类型转换可能会导致转换结果具备不同的正负号、不同的数量级,因此,转换过程很可能会导致数值丢失精度。
尽管数据类型窄化转换可能会发生上限溢出、下限溢出和精度丢失等情况,但是Java虚拟机规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常
c.补充说明:
当将一个浮点值窄化转换为整数类型T (T限于int或long类型之一)的时候,将遵循以下转换规则:
如果浮点值是NaN,那转换结果就是int或long类型的0。
如果浮点值不是无穷大的话,浮点值使用IEEE754的向零舍入模式取整,获得整数值v,如果v在目标类型T(int或long)的表示范围之内,那转换结果就是v。否则,将根据v的符号,转换为T所能表示的最大或者最小正数
当将一个double类型窄化转换为float类型时,将遵循以下转换规则:
通过向最接近数舍入模式舍入一个可以使用float类型表示的数字。最后结果根据下面这3条规则判断:
如果转换结果的绝对值太小而无法使用float来表示,将返回 float类型的正负零。
如果转换结果的绝对值太大而无法使用 float来表示,将返回 float类型的正负无穷大。
对于double类型的NaN值将按规定转换为 float类型的NaN值。
5.方法调用与返回指令:
Java是面向对象的程序设计语言,虚拟机平台从字节码层面就对面向对象做了深层次的支持。有一系列指令专门用于对象操作,可进一步细分为创建指令、字段访问指令、数组操作指令、类型检查指令
5.1.创建指令
虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:
1)创建类实例的指令:
创建类实例的指令: new
它接收一个操作数,为指向常量池的索引,表示要创建的类型,执行完成后,将对象的引用压入栈。
2)创建数组的指令:
创建数组的指令:newarray、anewarray、multianewarray。
newarray:创建基本类型数组
anewarray:创建引用类型数组
multinewarray:创建多维数组
上述创建指令可以用于创建对象或者数组,由于对象和数组在Java中的广泛使用,这些指令的使用频率也非常高。
5.2.字段访问指令:
对象创建后,就可以通过对象访问指令获取对象实例或数组实例中的字段或者数组元素。
访问类字段(static字段,或者称为类变量)的指令: getstatic. putstat
访问类实例字段(非static字段,或者称为实例变量)的指令:getfield、 putfield
举例:
以getstatic指令为例,它含有一个操作数,为指向常量池的Fieldref索引,它的作用就是获取Fieldref指定的对象或者值,并将其压入操作数栈。
public void sayHello(){
system.out.println( “hello” );
}
对应的字节码指令:
图示:
注意:get是入栈,而put是出栈
5.3.数组操作指令:
1)数组操作指令主要有: xastore和xaload指令。具体为:
把一个数组元素加载到操作数栈的指令: baload、caload、saload、iaload、laload、faload、daload、aaload
将一个操作数栈的值存储到数组元素中的指令: bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore
取数组长度的指令:arraylength
该指令弹出栈顶的数组元素,获取数组的长度,将长度压入栈。
2)说明
指令xaload表示将数组的元素压栈,比如saload、caload分别表示压入short数组和char数组。指令
xaload在执行时,要求操作数中栈顶元素为数组索引i,栈顶顺位第2个元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入堆栈。
xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。
5.4.类型检查指针:
类型检查指令
检查类实例或数组类型的指令:instanceof、checkcast。
指令checkcast用于检查类型强制转换是否可以进行。如果可以进行,那么checkcast指令不会改变操作数栈,否则它会抛出ClasscastException异常。
指令instanceof用来判断给定对象是否是某一个类的实例,它会将判断结果压入操作数栈。