灵魂拷问:为什么short、byte会被提升为int?boolean到底多大?


灵魂拷问:为什么short、byte会被提升为int?boolean到底多大?

文章插图
 
为什么short、byte会被提升为 int ?在学习JAVA语法的时候,知道short 、byte、byte 类型在做运算符号的时候,都会默认提升为 int,例如下面的代码就是无法通过编译的,需要将等于号右边的强制转为 short 才可以 。
public static void main(String[] args) {short a = 1;short b = 2;a = a + b;// 编译不过short c = a + b;// 编译不过}为什么两个 short 相加会变成 int,有的人解释说,两个 short 相加可能溢出,所以用 int 来接就不会溢出,那这样的话,两个 int 相加岂不应该是 long 类型吗?其实本质的原因要从字节码开始讲起 。
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表次操作所需参数(称为操作数,Operands)而构成 。
【灵魂拷问:为什么short、byte会被提升为int?boolean到底多大?】Java虚拟机的指令集中的大多数都对它们执行的操作的数据类型进行编码,例如 iload 指令,是将一个局部变量加载到操作栈,且这个局部变量必须是 int 类型 。
由于操作码的长度为一个字节,这意味着指令集的操作码总数不可能超过256条,这也为设计包含数据类型的操作码带来了很大压力:如果每一种与数据类型相关的指令都支持Java虚拟机所有运行时数据类型的话,那指令的数量就会超出一个字节所能表示的数量范围了 。
根据下表(出自 Table 2.11.1-A. Type support in the Java Virtual machine instruction set),可以发现大部分指令都没有支持 byte、char 和 short 类型,甚至没有任何指令支持 boolean 类型 。编译器会在编译器或运行期将 byte 和 short 类型带符号扩展为 int 类型,boolean 和 char 类型零位扩展为相应的 int 类型 。与之类似,在处理 boolean、byte、char 和 short 类型的数组时,也会转为使用相应的 int 类型的字节码来处理指令 。 因此,大多数对于 boolean、byte、char 和 short 类型数据的操作,实际都是使用 int 类型作为运算类型 。另外还有第二点原因,在设计虚拟机时,主要考虑的是 32位体系,32位系统使用 4 字节是最节省,因为 CPU 只能 32位32位的寻址 。
如果想详细查看各个指令,可以参考Java虚拟机规范

灵魂拷问:为什么short、byte会被提升为int?boolean到底多大?

文章插图
 
Java 中 boolean 到底多大?我们继续深入思考,boolen 到底有多大? 在学 Java 的时候,都说 byte、boolen 类型占 1字节,但上面又提到,byte 会被提升为 int 类型,那么就应该占了 4字节 。没错,虚拟机规范只有 4字节 和 8字节类型(long、float),boolean、char、short 都是占了 4字节 。
我们来看一例子 。
public class Test {byte aByte = 2;short aShort = 3;public void byteAdd() {aByte = (byte) (aByte + 1);}public void shortAdd() {aShort = (short) (aShort + 1);}}先编译此文件javac Test.java,查看 Class 内容,javap -verbose Test,摘取关键信息:
{byte aByte;descriptor: Bflags:short aShort;descriptor: Sflags:public void byteAdd();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: aload_02: getfield#2// Field aByte:B5: iconst_16: iadd7: i2b8: putfield#2// Field aByte:B11: returnpublic void shortAdd();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: aload_02: getfield#3// Field aShort:S5: iconst_16: iadd7: i2s8: putfield#3// Field aShort:S11: return}观察这两个方法,第一第二行目的是将对应的变量压入栈,第五行都是 iconst_1,将 int 类型的 1 压入栈中,然后使用 iadd 方法,将两个值相加,之后一个调用 i2b,一个调用 i2s 指令 。我们随便查看一个i2s的命令介绍 jvms-6.5.i2s,它是这样描述的
The value on the top of the operand stack must be of type int. It is popped from the operand stack, truncated to a short, then sign-extended to an int result. That result is pushed onto the operand stack.
翻译过来大致是:
操作数堆栈顶部的值必须是int类型 。它从操作数堆栈中弹出,截断为short,然后符号扩展为int结果 。结果被推送到操作数堆栈上 。
因此,可以看出 short、char 实际上也是占用了 和 int 一样大的字节的 。那我们平时所说 short 是 2 字节的岂不是错误的?并不是,对于单个 byte、char、short 类型的数据,在内存中实际会占 4 字节,但这对于数组来说并不适用,byte 数组每个元素占 1 字节,char、short 数组都占 2 字节 。


推荐阅读