下面这段代码大家应该多很熟悉,也都知道Java赋值long类型变量时,一定要在常量后面加上“L”或“l”。这篇笔记就简单展开讨论其原因。
1 | long a = 500; //赋值500,编译通过 |
这里其实涉及到一个细节,Java默认的整数常量类型为int。因此我们赋值变量时,不管变量是什么类型,赋值等号右边的数字默认先使用int保存,然后赋值时再根据变量类型进行自动类型转换。比如 byte a = 5
实际上是将int类型的‘5’,通过自动类型转换为byte类型的变量a。由于long类型的变量可能保存远远大于int表示范围的数值,因此可能出现大于int表示范围的数值常量默认保存在int中的情况。具体参照上述code block中的第二行,该赋值语句执行的过程是:
1. 现将50亿数值保存为默认的整数常量类型int;
2. 将该int类型的数值通过自动转型赋值给long类型的变量b;
实际上在执行第一步时就已经出错了,int的表示范围大概在正负21亿左右,50亿远远出超出该范围。具体报错信息如下:
这种报错和赋值号左边是否是long类型无关,只与数值常量大小是否超出int表示范围有关。比如 byte a = 5000000000
同样会报出 “integer number too large” 的错误。如果想要保存类似50亿这样的大数值,解决方法便是在数值常量后加上“L”, 这样使得Java不再默认使用int类型保存数字而是改为直接用long类型来保存。如此一来,对于 long c = 5000000000L
这样的赋值语句当然就不会再报错,但是如果是 byte a = 5000000000L
呢?这样在赋值语句执行的两个步骤中,虽然第一步数值常量默认保存为long类型不会报错了,但是第二步将long类型自动转型给byte类型却会出现精度丢失(50亿远大于byte的表示范围),因此会报出第二种错误:
很明显这是一个类型转换的错误,我们知道类型转换分为自动类型转换和强制类型转换,自动类型转换只能是由容量小的类型向容量大的类型转换(向上转型,这里说的容量是指表数范围而不是指字节数目,比如8字节的long可以自动转型为4字节的float),否则则需要强制类型转换。此时由long向byte显然无法进行自动类型转换,因此报出incompatible types的错误。这里又引出了第二个问题,为什么 byte a = 5
这样的赋值语句不会报出类型转换的错误呢?常量数值5也是默认保存为int类型,此时int转byte是向下转型,按道理是应该强制类型转换的。这里其实涉及到自动类型转换的一个特例:整型常量以默认类型int直接赋值给byte, short, char等类型变量时,只要该整型常量不超过对应变量类型的表数范围时,是可以进行自动类型转换的。更深一层的原因可能是由于JVM内部实际上是将char, byte, short扩展成int处理的,这里暂时不做深究。
需要强调的是,必须得是整型常量而且必须是直接赋值才能进行上述的特例转换。举个反例加深印象:
1 | int a = 10; |
此时会报出incompatible types的错误,简单来说,可以这样理解:编译器在编译时首先如果发现右边为int类型并且为常量,则会判断该常量是否在被赋值变量类型表数范围内,如果在则自动转型,不在,则报错要求强制转型。如果发现右边为变量,如果满足向上转型的标准则可自动转型,否则报错要求强制转型。下面还有一个类似的小例子:
1 | byte a = 1, b = 2, c, d; |
当算数运算操作对象是byte, short, char时,Java会将操作数提升为int类型来处理,因此第三行右边为int类型,然而编译器在编译过程中无法得知两个变量的相加结果,因此无法进一步判断是否在byte的范围内,因此无法自动转型;相反第二行代码是两个常量int相加,编译阶段就可以得到结果为3,在byte的范围内,自动转型。