为什么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)

313552 次浏览

与这些问题一样,JLS拥有答案。在这种情况下§15.26.2复合赋值运算符。摘录:

形式为E1 op= E2的复合赋值表达式等价于E1 = (T)((E1) op (E2)),其中TE1的类型,除了E1只计算一次。

引用的例子§15.26.2

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

short x = 3;x += 4.6;

并导致x具有值7,因为它等价于:

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

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

i = i + l的情况下,您需要从long转换为intexplicitly,然后它将编译并给出正确的输出。喜欢

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.

但是在+=的情况下,它可以正常工作,因为运算符隐式地从右变量的类型转换为左变量的类型,所以不需要显式转换。

非常好的问题。Java语言规范证实了你的建议。

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

short x = 3;x += 4.6;

并导致x具有值7,因为它等价于:

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

是的,

基本上当我们写

i += l;

编译器将其转换为

i = (int)(i + l);

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

真的是一件好事

这种转换的一个很好的例子是使用*=或/=

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'

这里的问题涉及类型转换。

当您添加int和long时,

  1. int对象被转换为long&两者都被添加,你得到long对象。
  2. 但是long对象不能隐式转换为int。因此,您必须显式执行此操作。

但是+=的编码方式是类型转换。i=(int)(i+m)

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

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

The same will not work the other way round. For example we cannot automatically convert a long to an int because the first requires more storage than the second and consequently information may be lost. To force such a conversion we must carry out an explicit conversion.
Type - Conversion

有时,这样的问题可以在面试中被问到。

例如,当你写:

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

主要区别在于,对于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);

这里的微妙之处…

j是双精度且i是int时,i+j有一个隐式类型转换。Java一直都是将整数转换为双精度,当它们之间存在操作时。

为了澄清i+=j,其中i是整数,j是双精度,可以描述为

i = <int>(<double>i + j)

见:这种对隐式铸造的描述

在这种情况下,为了清晰起见,您可能希望将j转换为(int)

Java语言规范定义#0等价于#1,其中#2是#3的类型,#3计算一次

这是一个技术上的答案,但你可能想知道为什么会这样。好吧,让我们考虑下面的程序。

public class PlusEquals {public static void main(String[] args) {byte a = 1;byte b = 2;a = a + b;System.out.println(a);}}

这个程序打印什么?

你猜到3了吗?太糟糕了,这个程序无法编译。为什么?好吧,碰巧在Java定义为返回#0中添加了字节。我相信这是因为Java虚拟机没有定义字节操作来节省字节码(毕竟数量有限),使用整数运算是语言中公开的实现细节。

但是如果a = a + b不起作用,这意味着如果E1 += E2被定义为E1 = E1 + E2a += b将永远不适用于字节。正如前面的例子所示,情况确实如此。作为使+=运算符适用于字节和短路的黑客,涉及一个隐式转换。这不是一个很好的黑客,但在Java1.0工作期间,重点是首先发布语言。现在,由于向后兼容,Java1.0中引入的这个黑客无法删除。