为啥 Java 中的复合赋值不能捕获溢出问题?

Posted

技术标签:

【中文标题】为啥 Java 中的复合赋值不能捕获溢出问题?【英文标题】:Why doesn't compound assignment in Java catch overflow problems?为什么 Java 中的复合赋值不能捕获溢出问题? 【发布时间】:2011-07-25 18:01:00 【问题描述】:

令我震惊的是,以下代码甚至可以在没有警告的情况下编译:

public void test()

    int value = 2000000000;
    long increment = 1000000000;
    value += increment;

而正如您所料,这会产生编译时错误:

public void test()

    int value = 2000000000;
    long increment = 1000000000;
    value = value + increment;

我查了一下,确实,JLS(第 15.26.2 节)有这样的说法:

E1 op = E2 形式的复合赋值表达式等价于 E1 = (T) ((E1) op (E2)),其中 T 是 E1 的类型,除了对 E1 求值 只有一次。

这对我来说似乎很荒谬。为什么他们觉得有必要在这里明确投射?似乎自动类型转换无论如何都会处理扩大,并且像这样自动缩小几乎肯定会导致整数溢出。

【问题讨论】:

好问题。对我来说从来没有多大意义。也许 James Gosling 会回应,因为他现在是 Google 员工,应该有很多空闲时间...... @squawknull - 是什么让您认为 Google 员工会在毫无意义的 SO 辩论上浪费他宝贵的“每周一天”? :-) @Stephen C:这就是我今天的笑点。 【参考方案1】:

Here 是一种解释:

当您进行赋值时(第一个代码 sn-p),java 会强制执行类型检查,因为 LHS 和 RHS 很可能彼此独立。

但是复合运算符更像 增量运算符。 += 修改变量的值 参与,而不是分配一个新的 变量的值。当你修改 一个字节,你期望一个字节作为 结果。为了让生活更轻松,java 确实 隐式类型转换 复合运算符,因为它们是 修饰符。

【讨论】:

【参考方案2】:

此链接已分析您提出的问题。

Varying behavior for possible loss of precision

为避免令人不快的意外,请勿 使用复合赋值运算符 byte、short 或 字符。使用复合赋值时 int 类型变量的运算符, 确保上的表达式 右手边不是长型的, 浮动,或双。使用化合物时 变量的赋值运算符 输入浮点数,确保表达式 右侧不是类型 双倍的。这些规则足以 防止编译器生成 危险的变窄管型。

【讨论】:

【参考方案3】:

复合赋值运算符由 JLS (15.26.2) 指定如下:

E1 op= E2 形式的复合赋值表达式等价于

      E1 = (T)((E1) op (E2))`, 

其中 T 是 E1 的类型,除了 E1 只计算一次。"

在这种情况下,E1 的类型为 int E2 的类型为 long,而 op 的类型为 +。所以这相当于:

value = (int)(value + increment);

intlong 的相加得到 long,然后在分配之前将其转换回 int。这一切都很好,因此没有编译错误。

这与简单赋值(即value = value + increment;)的区别在于简单赋值没有类型转换。


好的,那么为什么他们会这样定义?

我认为原因是为了让这样的例子起作用:

    byte b = ...
    b += 1;

如果没有类型转换,b += 1 将是编译错误,您需要将其编写为:

    b += (byte) 1;

【讨论】:

你不认为即使是字节也允许它是危险的吗?如果该代码不是加 1,而是加 128,该怎么办?

以上是关于为啥 Java 中的复合赋值不能捕获溢出问题?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Java 没有条件与和条件或运算符的复合赋值版本? (&&=, ||=)

为啥不能为 Java 中的 var 关键字分配 lambda 表达式?

Java异常处理之数字溢出问题

Java中foreach为啥不能给数组赋值

Expression 常用操作符,表达式

Expression 常用操作符,表达式