Java 为什么编译器没有给出此加法操作的错误?

Java 为什么编译器没有给出此加法操作的错误?,java,Java,我知道编译器对整数文本进行隐式类型转换。 例如: byte b = 2; // implicit type conversion, same as byte b = (byte)2; 如果范围溢出,编译器会给我一个错误: byte b = 150; // error, it says cannot convert from int to byte 当向变量传递表达式时,编译器会给出相同的错误: byte a = 3; byte b = 5; byte c = 2 + 7; // compil

我知道编译器对整数文本进行隐式类型转换。 例如:

byte b = 2; // implicit type conversion, same as byte b = (byte)2;
如果范围溢出,编译器会给我一个错误:

byte b = 150; // error, it says cannot convert from int to byte
当向变量传递表达式时,编译器会给出相同的错误:

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
我得出的结论是,包含变量的表达式的结果无法保证。结果值可以在字节范围之内或之外,所以编译器会抛出错误

让我困惑的是,当我这样说时,编译器不会抛出错误:

byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?
为什么它不给我一个错误

/*
 * 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;
    }
}
在反编译代码之后

class Test{
public static void main(String... args){
byte a = 127;
byte b = 5;
byte z = (a+=b); // no error, why ?
}
}

在内部,Java将
a+=b
运算符替换为
(字节)(n+n2)
代码。

基本原因是,当涉及常量时,编译器的行为略有不同。所有整数文本都被视为
int
常量(除非它们的末尾有
L
L
)。通常,您不能将
int
分配给
字节
。然而,有一个特殊的规则,其中涉及常数;看见基本上,在像
字节b=5这样的声明中
5
是一个
int
,但是将
转换为
字节是合法的,因为
5
是一个常量,因为它符合
字节的范围。这就是为什么允许
字节b=5
,而不允许
字节b=130

然而,
字节z=(a+=b)是另一种情况
a+=b
只需将
b
添加到
a
,并返回
a
的新值;该值被分配给
a
。由于
a
是一个字节,因此不需要进行缩小转换,而是将一个字节分配给一个字节。(如果
a
int
,则该程序始终是非法的。)

规则说
a+b
(因此
a=a+b
,或
a+=b
)不会溢出。如果在运行时,结果对于一个字节来说太大,那么上面的位就丢失了——值会自动换行。此外,编译器不会“值跟随”以注意到
a+b
将大于127;即使我们可以判断该值将大于127,编译器也不会跟踪以前的值。据它所知,当它看到
a+=b
时,它只知道程序在运行时将
b
添加到
a
,而不查看以前的声明来查看值。(一个好的优化编译器实际上可能会做这类工作。但我们讨论的是如何使程序合法或不合法,关于合法性的规则本身并不涉及优化。)

我得出的结论是,包含变量的表达式的结果无法保证。结果值可以在字节范围之内或之外,所以编译器会抛出错误

不,那不是原因。静态类型语言的编译器是这样工作的:任何变量都必须声明和类型化,因此即使在编译时它的值未知,它的类型也是已知的。隐式常量也是如此。基于这一事实,计算比例的规则基本上如下:

  • 任何变量都必须具有与其右侧表达式相同或更高的比例
  • 任何表达式都具有与其所涉及的最大项相同的比例
  • 一个明确的施法力量,例如,右边表达式的比例
(这些实际上是一个简化的视图;实际上可能更复杂一些)

将其应用于您的案例:

byte d = 1 + b
实际比例为:

byte = int + byte
byte = byte += byte
。。。(因为
1
被视为隐式
int
常量)。因此,应用第一条规则,变量必须至少具有
int
scale

在这种情况下:

byte z = (a+=b);
实际比例为:

byte = int + byte
byte = byte += byte
。。。没关系

更新

那么,为什么
字节e=a+b
会产生编译时错误呢


正如我所说,java中的实际类型规则更为复杂:虽然一般规则适用于所有类型,但原语
byte
short
类型受到更大的限制:编译器假设添加/减去两个或更多字节/短字符有可能导致溢出(如@Makoto所述),因此,它需要存储为下一种被认为是“更安全”的类型:反编译代码时,一个
int

将解释Java在做什么,它这样做的原因通常可以在语言规范中找到。但在此之前,我们必须确立几个重要概念:

  • 如果整型文字以ASCII字母L或L(ell)作为后缀,则其类型为long;否则为int型(§4.2.1)

  • 如果试图分配一个大于可容纳它的类型的文本,将导致编译错误。这是您遇到的第一个场景

所以我们回到这个场景:为什么添加两个明显超过一个字节所能处理的字节不会产生编译错误

它不会引发运行时异常,因为。 在这种情况下,两个数字加在一起会突然产生一个非常小的数字。由于
字节
的范围较小,极易溢出;例如,将1添加到127就可以了,结果是-128

它的主要原因在于Java处理原语值转换的方式;在这种情况下,我们谈论的是。也就是说,即使生成的和大于
字节
,缩小转换将导致丢弃信息,以允许数据放入
字节
,因为此转换不会导致运行时异常

打破僵局
long distInTicks = Math.round(getDistance() * 2.54);