Why does the compiler not give an error for this addition operation?
我知道编译器对整型文本执行隐式类型转换。例如:
1 | byte b = 2; // implicit type conversion, same as byte b = (byte)2; |
如果范围溢出,编译器会给我一个错误:
1 | byte b = 150; // error, it says cannot convert from int to byte |
当向变量传递表达式时,编译器会给出相同的错误:
1 2 3 4 5 | byte a = 3; byte b = 5; byte c = 2 + 7; // compiles fine byte d = 1 + b; // error, it says cannot convert from int to byte byte e = a + b; // error, it says cannot convert from int to byte |
我得出的结论是,涉及变量的表达式的结果是不能保证的。结果值可以在字节范围之内或之外,因此编译器会抛出一个错误。
令我困惑的是,当我这样放置编译器时,它不会抛出错误:
1 2 3 | byte a = 127; byte b = 5; byte z = (a+=b); // no error, why ? |
为什么它不给我一个错误?
虽然反编译您的代码将解释Java正在做什么,但它为什么这样做通常可以在语言规范中找到。但在开始之前,我们必须建立一些重要的概念:
字面数字总是被当作一个
int 来使用。
An integer literal is of type long if it is suffixed with an ASCII letter L or l (ell); otherwise it is of type int (§4.2.1).
byte 只能包含-128和127之间的整数值。如果试图分配的文本大于可容纳该文本的类型,则会导致编译错误。这是您遇到的第一个场景。
所以我们回到这个场景:为什么添加两个明显大于字节所能处理的字节不会产生编译错误?
它不会因为溢出而引发运行时异常。在这种情况下,两个数字加在一起会突然产生一个非常小的数字。由于
它的主要原因是Java处理原始值转换的方式;在这种情况下,我们讨论的是缩小转换。也就是说,即使产生的总和大于
要逐步分解场景:
- Java将EDCOX1×5和EDCOX1〔6〕相加在一起产生132。
- Java理解EDCOX1×7和EDCOX1×8是EDCOX1×1的类型,所以结果也必须是EDCOX1×1的类型。
- 这个整数的结果仍然是132,但是在这一点上,Java将执行一个CAST以将结果缩小到一个字节内——有效地给您EDCOX1×11。
- 现在,由于环绕,
a 和z 都包含结果-124。
答案由JLS 15.26.2提供:
For example, the following code is correct:
short x = 3;
x += 4.6; and results in x having the value 7 because it is equivalent to:
short x = 3;
x = (short)(x + 4.6);
因此,正如您所看到的,最新的情况实际上是有效的,因为加法赋值(与任何其他运算符赋值一样)对左侧类型执行隐式强制转换(在您的情况下,
I came to the conclusion that the result of an expression that involves variables cannot be guaranteed. The resulting value can be within or outside the byte range so compiler throws off an error.
不,这不是原因。静态类型语言的编译器是这样工作的:任何变量都必须声明和类型化,因此即使在编译时它的值未知,它的类型也是已知的。隐式常量也是如此。基于这一事实,计算比例的规则基本上是:
- 任何变量的小数位数必须与表达式右侧的小数位数相同或更高。
- 任何表达式都具有与所涉及的最大项相同的比例。
- 一个明确的铸造力,corse,右侧表达式的比例。
(这些实际上是一个简化的视图;实际上可能更复杂一些)。
适用于您的案例:
1 | byte d = 1 + b |
实际比例为:
1 | byte = int + byte |
…(因为
在这种情况下:
1 | byte z = (a+=b); |
实际比例为:
1 | byte = byte += byte |
…没关系。
更新
那么,为什么
正如我所说的,Java中的实际类型规则更为复杂:虽然一般规则适用于所有类型,但原始EDCOX1 6和EDCOX1 36种类型更受限制:编译器假定添加/减除两个或多个字节/短裤可能导致溢出(如@ Makto声明),因此需要存储为下一个类型。规模被认为是"更安全":一个
我以前在一个项目中遇到过这种情况,我学到了:
与C/C++不同,Java总是使用有符号的原语。一个字节是从-128到+127,所以如果在这个范围之后分配任何内容,它将给您带来编译错误。
如果您显式地转换为像
当使用像
关于您的问题,为什么编译器不会在诸如
从OpenJDK中挖掘Java编译器
http://hg.openjdk.java.net/jdk9/jdk9/langtools.
我跟踪了操作数的树处理过程,在一个编译器文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void visitAssignop(final JCAssignOp tree) { ... Symbol newOperator = operators.resolveBinary(tree, newTag, tree.type, tree.rhs.type); JCExpression expr = lhs; //Interesting part: if (expr.type != tree.type) expr = make.TypeCast(tree.type, expr); JCBinary opResult = make.Binary(newTag, expr, tree.rhs); opResult.operator = newOperator;: .... |
如您所见,如果
基本原因是编译器在涉及常量时的行为略有不同。所有整型文字都被视为
但是,
规则规定,
表达
请注意,Java的类型系统最初是为了简单起见而设计的,它的规则在很多情况下被选择为有意义的,但是因为使规则简单化比使它们始终合理更重要,所以有许多类型系统规则产生无意义行为的情况。其中一个更有趣的例子是:
当然,在现实世界中,人们不会试图将长常量取整,但如果出现以下情况,情况可能会出现:
1 |
已更改以消除比例因子[并且getDistance()返回long]。您希望l1和l2能收到什么值?你能弄明白他们为什么会得到其他价值吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* * Decompiled Result with CFR 0_110. */ class Test { Test() { } public static /* varargs */ void main(String ... arrstring) { int n = 127; int n2 = 5; byte by = (byte)(n + n2); n = by; byte by2 = by; } } |
在代码反编译之后
1 2 3 4 5 6 7 | class Test{ public static void main(String... args){ byte a = 127; byte b = 5; byte z = (a+=b); // no error, why ? } } |
在内部,Java用EDCOX1×1代码替换了您的EDCOX1×0运算符。