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


3633

直到今天,我还以为例如:

i += j;

只是以下方面的捷径:

i = i + j;

但是,如果我们尝试这样做:

int i = 5;
long j = 8;

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

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


135
令我惊讶的是,Java允许使用这种语言,因为它比以前的语言更严格。转换错误可能会导致严重故障,就像Ariane5 Flight 501的情况一样,将64位float转换为16位整数会导致崩溃。
SQLDiver

103
在用Java编写的飞行控制系统中,这将是您最少的后顾之忧@SQLDiver
Ross Drew

10
实际上i+=(long)j;甚至会编译良好。
Tharindu Sathischandra

6
一组开发人员不断推动准确性而另一组开发人员不断推动其使用非常有趣。我们几乎需要两种语言版本,一种非常精确,一种易于使用。从两个方向推动Java使其不适合任何一个群体。
比尔

5
如果确实需要铸造,您会放在哪里?i += (int) f;在加法之前强制转换f,所以它不是等效的。(int) i += f;赋值后强制转换结果,也不相等。没有地方进行强制转换,表示您要在添加后但在赋值之前强制转换值。
Norill Tempest

Answers:


2441

与这些问题一样,JLS保留了答案。在这种情况下,第1.5.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);

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


42
i+=j我自己检查时会这样编译,但这会导致精度损失,对吗?如果是这样,为什么它也不允许它在i = i + j中发生?为什么在这里烦我们?
bad_keypoints 2012年

46
@ronnieaka:我猜语言设计者认为,在一种情况下(i += j),它是安全的假设,相对于其他案件的精度损失需要(i = i + j
卢卡斯埃德尔

12
不,就在我面前!对不起,我没注意到。就像在您的答案中一样E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)),所以有点像隐式向下的类型转换(从long到int)。而在i = i + j中,我们必须明确地做到这一点,即(T)E1 = ((E1) op (E2))“不是”中提供部分吗?
bad_keypoints 2012年

11
Java编译器之所以添加类型转换的可能原因是,如果您要对不兼容的类型执行算术运算,则无法使用协定形式对结果进行类型转换。结果的类型转换通常比问题论证的类型转换更准确。使用不兼容的类型时,没有任何类型转换会使收缩无效,因为它总是会导致编译器抛出错误。
ThePyroEagle

6
它不是四舍五入的。演员表(=被截断)
卢卡斯·埃德

483

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

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'

11
@AkshatAgarwal ch是一个字符。65 * 1.5 = 97.5->知道了吗?
萨哈尔·杜塔

79
是的,但是我可以看到一些初学者来了,读了这篇,然后以为可以乘以1.5来将任何字符从大写转换为小写。
达伍德·伊本·卡里姆

103
@DavidWallace只要有字符就可以A;)
彼得·劳瑞

14
@PeterLawrey和@DavidWallace我会透露您的秘密 - ch += 32 = D
Minhas Kamal

256

很好的问题。在Java语言规范确认您的建议。

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

short x = 3;
x += 4.6;

并导致x的值为7,因为它等效于:

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

15
或更有趣:“ int x = 33333333; x + = 1.0f;”。
超级猫

5
@supercat,这是什么骗术?舍入不正确的四舍五入的加宽转换,然后加上实际上没有改变结果的加法运算,再次将其转换为int以产生对于正常人而言最不可预料的结果。
neXus

1
@neXus:恕我直言,由于double->float类型的值float标识的实数比类型的标识少,所以转换规则应该被视为扩大规则double。如果一个人将其double视为完整的邮政地址和float5位数的邮政编码,则可以满足给定完整地址的邮政编码要求,但是仅给出邮政编码就不可能准确地指定对完整地址的要求。将街道地址转换为邮政编码是一项有损操作,但是 ...
超级猫

1
...需要完整地址的人通常不会只要求邮政编码。从转换float->double等同于用“美国邮政局,比佛利山庄CA 90210”转换美国邮政编码90210。
超级猫

181

是,

基本上当我们写

i += l; 

编译器将此转换为

i = (int)(i + l);

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

真是一件好事


3
你能告诉我这是哪个类文件吗?
nanofarad 2013年

6
@hexafraction:类文件是什么意思?如果您问的是我在帖子中提到的类文件,而不是它是Java类的
编译

3
哦,您提到了“ the”类文件代码,这使我相信其中涉及特定的类文件。我明白你的意思了。
nanofarad 2013年

@Bogdan正确使用字体应该不成问题。选择错误字体进行编程的程序员应该清楚地考虑如何进行操作……
glglgl 2015年

7
@glglgl我不同意在这种情况下应该依靠字体来区分……但是每个人都可以自由选择认为最好的东西。
Bogdan Alexandru

92

您需要从强制转换longint 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.

但在这种情况下,+=因为操作符可以隐式地执行从右变量类型到左变量类型的类型转换,所以它工作得很好,因此无需显式转换。


7
在这种情况下,“隐式转换”可能是有损的。实际上,就像@LukasEder在他的回答中指出的那样,对的强制转换int是在之后执行的+。编译器将(应该?)抛出一个警告,如果它真的投的longint
罗曼(Romain)2012年

63

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

当您添加int和long时,

  1. 将int对象强制转换为long,并且将两者都添加,就得到long对象。
  2. 但是长对象不能隐式转换为int。因此,您必须明确地做到这一点。

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


54

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

 字节->短->整数->长->浮点->双精度 

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


2
嘿,但long比的大2倍float
显示名称

11
A float不能容纳所有可能的int值,而A double不能容纳所有可能的long值。
Alex MDC 2013年

2
“安全转换”是什么意思?从答案的后半部分,我可以推断出您的意思是自动转换(隐式强制转换),在float-> long的情况下当然是不正确的。浮动pi = 3.14f; 长b = pi; 将导致编译器错误。
路加福音

1
您最好将浮点图元类型与整数图元类型区分开。他们不是一回事。
ThePyroEagle 2015年

Java具有简单化的转换规则,该规则要求在许多模式中使用强制类型转换,否则没有强制类型转换的行为否则会符合预期,但是不需要在许多通常错误的模式中进行强制转换。例如,double d=33333333+1.0f;即使结果33333332.0可能不是预期的结果,编译器也将毫无保留地接受(顺便说一下,33333334.0f的算术正确答案可以表示为floatint)。
超级猫

46

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

例如,当您编写时:

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

6
感谢您对opC ++使用与Java使用之间的比较的见解。我总是喜欢看到这些琐事,而且我确实认为它们为谈话中的某些事情提供了可能经常被忽略的地方。
汤玛斯(Thomas)

2
但是问题本身有趣,在面试中问这个问题很愚蠢。这并不能证明该人可以产生高质量的代码,而只是证明他有足够的耐心来准备Oracle证书考试。通过使用危险的自动转换来“避免”不兼容的类型,从而隐藏可能的溢出错误,甚至可能证明该人无法产生可能的高质量代码。该死的所有这些自动转换和自动装箱的Java作者!
Honza Zidek '18

25

主要的区别是使用时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);

5
复合赋值运算符对二进制运算的结果(而不是右侧操作数)执行缩小的转换。因此,在您的示例中,“ a + = b”不等同于“ a = a +(int)b”,而是如此处其他答案所述,等于“ a =(int)(a + b)”。
卢布洛赫(Lew Bloch)2016年

13

这里的微妙之处...

对于i+jwhen j是double和iint 有一个隐式类型转换。当它们之间存在运算时,Java 总是将整数转换为双精度

为了弄清楚i+=j哪里i是整数,j是双精度数,可以描述为

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

请参阅:此隐式转换描述

为了清楚起见j(int)在这种情况下,您可能需要键入。


1
我认为可能是更有趣的情况int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;。加上零someInt不会影响其价值,但提升someIntfloat可能会改变其价值。
超级猫

4

Java语言规范定义E1 op= E2为等同于E1 = (T) ((E1) op (E2))where T是type的类型,E1并且E1被评估一次

这是一个技术性的答案,但您可能想知道为什么是这种情况。好,让我们考虑以下程序。

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

该程序打印什么?

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

但是,如果a = a + b不起作用,则意味着a += b如果将其E1 += E2定义为,则永远不会对字节起作用E1 = E1 + E2。如前面的示例所示,情况确实如此。作为使+=运算符可以处理字节和短裤的技巧,其中涉及隐式强制转换。这并不是什么骇人听闻的事情,但是在Java 1.0工作期间,重点就在于开始发布该语言。现在,由于向后兼容,无法删除Java 1.0中引入的这种hack。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.