ChatGPT解决这个技术问题 Extra ChatGPT

为什么 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) 的快捷方式?

我很惊讶 Java 允许这样做,它是一种比它的前辈更严格的语言。转换错误可能导致严重故障,就像 Ariane5 Flight 501 的情况一样,将 64 位浮点转换为 16 位整数会导致崩溃。
在用 Java 编写的飞行控制系统中,这将是您最不担心的@SQLDiver
实际上 i+=(long)j; 甚至可以正常编译。
一组开发人员不断推动准确性和另一组开发人员的易用性真的很有趣。我们几乎需要两种版本的语言,一种非常精确,另一种易于使用。从两个方向推动 Java 会使它变得不适合任何一个群体。
如果它确实需要铸造,你会把它放在哪里? i += (int) f; 在加法之前强制转换 f,因此它不等价。 (int) i += f; 在赋值后转换结果,也不等价。没有地方可以放置一个表示您想要在添加之后但在分配之前强制转换值的转换。

F
Flow

与这些问题一样,JLS 给出了答案。在这种情况下 §15.26.2 Compound Assignment Operators。提取物:

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 = (短)(x + 4.6);

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


所以 i+=j 在我检查自己时编译,但它会导致精度损失,对吧?如果是这样,为什么它不允许它也发生在 i=i+j 中?为什么要在那里打扰我们?
@ronnieaka:我猜语言设计者认为在一种情况下(i += j),与另一种情况下(i = i + j)相比,假设精度损失是需要的更安全
P
Peter Lawrey

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

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'

@AkshatAgarwal ch 是一个字符。 65 * 1.5 = 97.5 -> 明白了吗?
是的,但我可以看到一些初学者来到这里,读到这里,然后走开,以为你可以通过将任何字符乘以 1.5 将其从大写转换为小写。
@DavidWallace 任何字符,只要它是 A ;)
@PeterLawrey & @DavidWallace 我会透露你的秘密- ch += 32 =D
K
Keiwan

非常好的问题。 Java Language specification 确认您的建议。

例如下面的代码是正确的:short x = 3; x += 4.6;并导致 x 的值为 7,因为它等价于:short x = 3; x = (短)(x + 4.6);


或者更有趣:“int x=33333333; x+=1.0f;”。
@supercat,这是什么诡计?不正确四舍五入的扩大转换,然后是实际上不会改变结果的加法,再次转换为 int 以产生对于正常人来说最出乎意料的结果。
@neXus:恕我直言,转换规则应该将 double->float 视为扩展,因为 float 类型的值识别实数的具体程度低于 double 类型的值。如果将 double 视为完整的邮政地址,将 float 视为 5 位邮政编码,则可以满足对给定完整地址的邮政编码的请求,但无法准确指定完整的邮政编码请求只给出邮政编码的地址。将街道地址转换为邮政编码是一项有损操作,但是...
...需要完整地址的人通常不会只要求邮政编码。从 float->double 转换相当于将美国邮政编码 90210 转换为“US Post Office, Beverly Hills CA 90210”。
p
psmears

是的,

基本上当我们写

i += l; 

编译器将其转换为

i = (int)(i + l);

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

真的很高兴知道


你能告诉我这是哪个类文件吗?
@hexafraction:类文件是什么意思?如果您询问我在帖子中提到的类文件,而不是您的 java 类的编译版本
哦,你提到了“类”文件代码,这让我相信涉及到一个特定的类文件。我现在明白你的意思了。
@Bogdan 正确使用字体应该不是问题。选择错误字体进行编程的程序员应该清楚地考虑如何进行......
@glglgl 我不同意在这些情况下应该依靠字体来区分......但是每个人都可以自由选择认为最好的东西。
d
dku.rajkumar

如果是 i = i + l,您需要从 long 转换为 int explicitly,然后它将编译并给出正确的输出。喜欢

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.

但在 += 的情况下,它工作得很好,因为运算符隐式地将类型转换从右变量的类型转换为左变量的类型,因此不需要显式转换。


在这种情况下,“隐式转换”可能是有损的。实际上,正如@LukasEder 在他的回答中所说,对 int 的强制转换是在 + 之后 执行的。如果确实将 long 转换为 int,编译器会(应该?)抛出警告。
s
slevy1

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

当你添加 int 和 long 时,

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

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


t
tinker_fairy

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

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

反过来也行不通。例如,我们不能自动将 long 转换为 int,因为第一个比第二个需要更多的存储空间,因此可能会丢失信息。要强制进行此类转换,我们必须执行显式转换。
Type - Conversion


嘿,但是 longfloat 大 2 倍。
float 不能保存所有可能的 int 值,double 不能保存所有可能的 long 值。
“安全转换”是什么意思?从答案的后半部分,我可以推断出您的意思是自动转换(隐式转换),这在 float -> long 的情况下当然不是真的。浮动 pi = 3.14f;长 b = pi;将导致编译器错误。
您最好将浮点原始类型与整数原始类型区分开来。他们不是一回事。
Java 有简单的转换规则,要求在许多模式中使用强制类型转换,如果没有强制类型转换的行为会符合预期,但在许多通常是错误的模式中不需要强制类型转换。例如,编译器将毫无怨言地接受 double d=33333333+1.0f;,即使结果 33333332.0 可能不是预期的结果(顺便提一下,33333334.0f 的算术正确答案可以表示为 floatint)。
T
Thomas

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

例如,当你写:

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

感谢您对 C++ 中的 op 使用与 Java 中的使用进行比较的见解。我总是喜欢看到这些琐事,而且我确实认为它们对可能经常被忽略的对话做出了贡献。
但是这个问题本身很有趣,在采访中问这个是愚蠢的。这并不能证明这个人可以编写出高质量的代码——它只是证明他有足够的耐心准备 Oracle 证书考试。并且通过使用危险的自动转换来“避免”不兼容的类型,从而隐藏可能的溢出错误,甚至可能证明该人无法产生可能的高质量代码。该死的Java作者所有这些自动转换和自动装箱等等!
R
Roy Shmuli

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

复合赋值运算符执行二元运算结果的缩小转换,而不是右手操作数。因此,在您的示例中,“a += b”不等于“a = a + (int) b”,但正如此处其他答案所解释的那样,“a = (int)(a + b)”。
B
Biddut

微妙的点在这里...

j 是 double 且 i 是 int 时,i+j 存在隐式类型转换。 Java ALWAYS 在整数之间存在运算时会将整数转换为双精度数。

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

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

请参阅:this description of implicit casting

为了清楚起见,在这种情况下,您可能需要将 j 类型转换为 (int)


我认为一个更有趣的案例可能是int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;。向 someInt 添加零不应影响其值,但将 someInt 提升为 float 可能会更改其值。
K
Konrad Borowski

Java 语言规范 defines E1 op= E2 to be equivalent to E1 = (T) ((E1) op (E2)) where T is a type of E1 and E1 is evaluated once

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

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

这个程序打印什么?

你猜对了3?太糟糕了,这个程序不会编译。为什么?好吧,在 Java is defined to return an int 中添加字节也是如此。我相信这是因为 Java 虚拟机没有定义字节操作来保存字节码(毕竟这些字节码的数量有限),而是使用整数操作是一种语言中公开的实现细节。

但是,如果 a = a + b 不起作用,这意味着如果将 E1 += E2 定义为 E1 = E1 + E2,则 a += b 将永远无法用于字节。正如前面的例子所示,情况确实如此。作为使 += 运算符适用于字节和短裤的一种技巧,其中涉及到隐式转换。这并不是什么大问题,但在 Java 1.0 工作期间,重点是从一开始就发布该语言。现在,由于向后兼容,Java 1.0 中引入的这个 hack 无法删除。