题 为什么Java的+ =, - =,* =,/ =复合赋值运算符需要转换?


直到今天,我还以为:

i += j;

只是一个捷径:

i = i + j;

但是如果我们尝试这个怎么办:

int i = 5;
long j = 8;

然后 i = i + j; 不会编译但是 i += j; 将编译好。

实际上这是否意味着什么? i += j; 这是这样的捷径 i = (type of i) (i + j)


3286
2018-01-03 10:10


起源


我很惊讶Java允许这样做,是一种比它的前辈更严格的语言。铸造中的错误可能导致严重故障,就像Ariane5 Flight 501的情况一样,64位浮点数转换为16位整数导致崩溃。 - SQLDiver
在用Java编写的飞行控制系统中,这将是您最不担心的@SQLDiver - Ross Drew
可能重复 java中a + = 10和a = a + 10之间的区别? - phuclv
其实 i+=(long)j; 甚至会编译好。 - Tharindu
我认为道格拉斯·克罗克福德毕竟赢得了争论......这种行为很糟糕,很多语言在转换方面都有类似的问题。 - Aluan Haddad


答案:


和往常一样,JLS都有答案。在这种情况下 §15.26.2复合赋值运算符。提取物:

表单的复合赋值表达式 E1 op= E2 相当于 E1 = (T)((E1) op (E2)),哪里 T 是的类型 E1, 除了那个 E1 仅评估一次。

引用的一个例子 §15.26.2

[...]以下代码是正确的:

short x = 3;
x += 4.6;

并且结果x的值为7,因为它相当于:

short x = 3;
x = (short)(x + 4.6);

换句话说,你的假设是正确的。


2212
2018-01-03 10:15



所以 i+=j 我检查自己编译,但它会导致精度损失吗?如果是这样的话,为什么不允许它在i = i + j中发生呢?为什么要在那里弄错 - bad_keypoints
@ronnieaka:我猜测语言设计师认为在一个案例中(i += j),假设与其他情况相反,期望精度损失更为安全(i = i + j) - Lukas Eder
不,它就在我面前!对不起,我之前没有注意到。在你的答案中, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)),这有点像隐含的类型转换(从long到int)。而在i = i + j中,我们必须明确地做,即提供 (T) 参与 E1 = ((E1) op (E2)) 不是吗? - bad_keypoints
我假设这用于避免显式转换和/或 f postfix在一个短的时候添加双字的情况? - Andrey Akhmetov
Java编译器为什么添加类型转换的一个可能原因是,如果您尝试对不兼容类型执行算术运算,则无法使用缩减形式对结果进行类型转换。结果的类型转换通常比有问题的参数的类型转换更准确。当使用不兼容的类型时,没有类型转换会使收缩变得无用,因为它总是会导致编译器抛出错误。 - ThePyroEagle


这个演员的一个很好的例子是使用* =或/ =

byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57

要么

byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40

要么

char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'

要么

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'

442
2018-01-03 10:20



@SajalDutta我没有得到那个参考。介意解释? - Akshat Agarwal
@AkshatAgarwal ch是一个char。 65 * 1.5 = 97.5 - >知道了? - Sajal Dutta
是的,但我可以看到一些初学者来到这里,阅读本文,并且认为你可以将任何字符从大写字母转换为小写字母,然后乘以1.5。 - Dawood ibn Kareem
@DavidWallace任何角色,只要它是 A ;) - Peter Lawrey
@PeterLawrey和@DavidWallace我会透露你的 秘密 - ch += 32  = d - Minhas Kamal


非常好的问题。该 Java语言规范 确认你的建议。

例如,以下代码是正确的:

short x = 3;
x += 4.6;

并且结果x的值为7,因为它相当于:

short x = 3;
x = (short)(x + 4.6);

223
2018-01-03 10:17



或者更有趣:“int x = 33333333; x + = 1.0f;”。 - supercat


是,

基本上我们写的时候

i += l; 

编译器将其转换为

i = (int)(i + l);

我刚检查了一下 .class 文件代码。

真的是一件好事


163
2018-01-03 10:19



你能告诉我这是哪个类文件吗? - Andrey Akhmetov
@hexafraction:你的类文件是什么意思?如果你问我在帖子中提到的类文件比你的java类的编译版本 - Umesh Awasthi
哦,你提到了“类”文件代码,这让我相信一个特定的类文件。我明白你的意思了。 - Andrey Akhmetov
在所有字母中,你实际上选择了“l”(小写L)...... - Bogdan Alexandru
@glglgl我不同意在这些情况下应该依靠字体来区分......但是每个人都可以自由选择最好的东西。 - Bogdan Alexandru


你需要施放 long 至 int  explicitly 的情况下 i = i + l  然后它将编译并提供正确的输出。喜欢

i = i + (int)l;

要么

i = (int)((long)i + l); // this is what happens in case of += , dont need (long) casting since upper casting is done implicitly.

但是如果是的话 += 它只是工作正常,因为运算符隐式执行从右变量类型到左变量类型的类型转换,因此无需显式转换。


85
2018-01-03 10:15



在这种情况下,“隐性演员”可能是有损的。实际上,正如@LukasEder在他的回答中指出的那样 int 执行 后 该 +。编译器会(应该?)抛出一个警告,如果它真的投了 long 至 int。 - Romain


这里的问题涉及类型铸造。

当你添加int和long时,

  1. int对象被转换为long并且两者都被添加并且你得到长对象。
  2. 但是长对象不能隐式地转换为int。所以,你必须明确地这样做。

+= 以这样的方式编码,即它进行类型转换。 i=(int)(i+m)


56
2018-01-03 10:20





在Java类型中,当赋值操作右侧的表达式类型可以安全地提升为赋值左侧的变量类型时,将自动执行转换。因此我们可以安全地分配:

 byte  - > short  - > int  - > long  - > float  - > double。 

反之亦然。例如,我们不能自动将long转换为int,因为第一个需要比第二个更多的存储,因此信息可能会丢失。要强制执行此类转换,我们必须执行显式转换。
类型 - 转换


47
2018-01-23 05:50



嘿,但是 long 是2倍大 float。 - Sarge Borsch
一个 float 不能抓住一切可能 int 价值,和 double 不能抓住一切可能 long 值。 - Alex MDC
“安全转换”是什么意思?从答案的后半部分我可以推断出你的意思是自动转换(隐式转换),当然,浮动 - >长的情况下,这当然不是真的。 float pi = 3.14f;长b = pi;将导致编译器错误。 - Luke
最好将浮点基元类型与整数基元类型区分开来。他们不是一回事。 - ThePyroEagle
Java具有简单的转换规则,需要在许多模式中使用强制转换,其中没有强制转换的行为否则会与期望匹配,但不需要许多模式中的强制转换,这些模式通常是错误的。例如,编译器将接受 double d=33333333+1.0f; 没有抱怨,即使结果33333332.0可能不是预期的(顺便说一下,33333334.0f的算术正确答案可以表示为 float 要么 int)。 - supercat


有时,在面试时可以提出这样的问题。

例如,当你写:

int a = 2;
long b = 3;
a = a + b;

没有自动类型转换。在C ++中,编译上面的代码不会有任何错误,但在Java中你会得到类似的东西 Incompatible type exception

所以要避免它,你必须编写如下代码:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting

37
2017-12-02 10:40



感谢您对比较的见解 op 在C ++中使用它在Java中的使用。我总是喜欢看到这些琐事,我认为他们为那些经常被遗漏的对话做出了贡献。 - Thomas
然而问题本身 是 有趣,问这个 在一次采访中 是愚蠢的。它并不能证明这个人能够提供高质量的代码 - 这只是证明他有足够的耐心来准备Oracle证书考试。并且通过使用危险的自动转换“避免”不兼容的类型,从而隐藏可能的溢出错误,甚至可能 证明 该人无法生成可能的高质量代码。该死的Java作者所有这些自动转换和自动拳击和所有! - Honza Zidek


主要区别在于 a = a + b,没有进行类型转换,因此编译器因为没有进行类型转换而对你生气。但随着 a += b,它真正做的是类型转换 b 与兼容的类型 a。所以,如果你这样做

int a=5;
long b=10;
a+=b;
System.out.println(a);

你真正在做的是:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);

18
2018-06-07 23:27



复合赋值运算符执行二进制运算结果的缩小转换,而不是右手操作数。所以在你的例子中,'a + = b'不等于'a = a +(int)b',但是,正如其他答案所解释的那样,'a =(int)(a + b)'。 - Lew Bloch