Java,“a[1] *= a[1] = b[n + 1]”没有按预期工作?

Java, "a[1] *= a[1] = b[n + 1]" not working as expected?

提问人:Hex Crown 提问时间:3/21/2018 更新时间:3/24/2018 访问量:102

问:

正如标题所示,我有一个使用临时数组的函数,我想从另一个数组中将一个值写入其中,然后将这两个值与自身相乘。

例:

float[] a = {0, 0}

a[0] *= a[0] = b[n    ];
a[1] *= a[1] = b[n + 1];

我希望上述内容能够做到以下几点:

a[0] = b[n    ];
a[0] *= a[0]; //(aka: a[0] = a[0] * a[0])

a[1] = b[n + 1];
a[1] *= a[1];

尽管这种行为似乎并非如此。相反,它似乎只是将“a”中的原始值与“b”中所包含的任何值相乘,如下所示:

a[0] = a[0] * b[n    ];
a[1] = a[1] * b[n + 1];

我一直认为,“=”之后的任何内容都会首先被评估,正如你这样做时所看到的那样:

float a, b;
a = b = 5;
//"a" and "b" both equal "5" now.

既然如此,那岂不是表明我原来的例子应该有效吗?

谁能解释一下发生了什么以及为什么这段代码不能按预期工作?

java 变量赋 法赋 值运算符

评论

1赞 Axel 3/21/2018
乍一看,这似乎是那些新手问题之一,但仔细想想,你是有道理的。但是,我永远不希望在生产代码中看到这一点。经验法则:写成更容易阅读的内容。像这样的代码只在编译器必须容纳大约 30k 内存的日子里有用,并且您可能会通过进行一些棘手的摆弄来节省一些 CPU 周期,因为编译器没有优化。现在没有时间,但在 Java 中,它应该全部在规范中设置(而在 C 中,我认为它要么是未定义的,要么是定义的实现)。a[0]=b[n]*b[n]

答:

2赞 Tim Biegeleisen 3/21/2018 #1

赋值运算符(与大多数其他运算符不同)在 Java 中从计算(文档)。这意味着以下内容:

a[0] *= a[0] = b[n];

实际上被评估为:

a[0] *= (a[0] = b[n]);

括号中的数量是一个赋值,返回值 ,但不更改 的值。然后,进行以下最终分配:b[n]a[0]

a[0] = a[0] * b[n]

两者都是赋值运算符,并且具有相同的优先级。因此,在这种情况下,从右到左的规则适用。*==

评论

1赞 Axel 3/21/2018
我认为这并不能真正说明为什么应该与 相同,因为人们可以期望在计算之后,的值被设置为 ,因此导致 。a[0] *= (a[0] = b[n])a[0] *= b[n](a[0] = b[n])a[0]b[n]a[0] = b[n]*b[n]
0赞 Hex Crown 3/21/2018
@Axel是的,这就是我认为会发生的,我什至尝试了你提到的,在发布这个问题之前,“a[0] *= (a[0] = b[n])”,看看括号是否有任何效果,当.java转换为 .class 文件时,它们最终被编译出来。挑衅地挠了挠头。
0赞 Tim Biegeleisen 3/21/2018
@Axel我已经解释了发生了什么,但我可能有点不对劲。如果你想贡献,我可以把它做成一个 Wiki 帖子。基本上,RHS 本身的赋值返回 ,然后对 进行另一个赋值。b[n]a[0]
0赞 Hex Crown 3/21/2018
@TimBiegeleisen 那么,实际上发生的事情是 (a[0] = b[n]) 的值被保存在一个“不可见”的临时变量中,因为 *= 运算符本质上不能同时读取和写入自身?
1赞 Axel 3/21/2018
嘿,一切都在规格中。阅读我的回答。首先计算 LHS 的值,因此 RHS 上的赋值对结果没有影响。
3赞 Matt 3/21/2018 #2

参考 Java 文档

除赋值运算符外,所有二元运算符都从左到右计算;赋值运算符从右到左计算。

因此,在您的案例中发生的情况是,您分配了 的值,然后将原始值乘以该新值。所以你的表情是有效的.a[0] *= a[0] = b[n ];a[0]b[n]a[0]a[0] *= b[n]

就个人而言,我不会在一行上两次使用赋值运算符,阅读起来可能会令人困惑。

评论

0赞 Axel 3/21/2018
如果不先评估 LHS,以后如何使用 LHS 的原始值?我认为您链接到的 Java 教程中的部分有点过于简单化了实际情况。在化合物赋值中,首先评估 LHS,然后评估 RHS,完成计算,然后为 LHS 上的变量分配计算结果。我认为你引用的段落试图阐明的是,这仅仅导致了被分配给 和 的价值。a=b=ccab
0赞 Matt 3/21/2018
我同意有一个简化,目前尚不清楚 LHS 与 RHS 有何不同,但首先明确评估 RHS,然后进行评估。在表达式中,先是,然后是 。a[0]a[0]a[0] = b[n]a[0](old) *= a[0](new)a = b = cb = ca = b
0赞 Axel 3/21/2018
首先被计算并存储为 。其余的都是正确的。a[0]a[0](old)
0赞 Matt 3/21/2018
我明白你的意思了。我试图强调分配的顺序,而不是存储 LHS 值的事实。
7赞 Axel 3/21/2018 #3

我认为到目前为止的答案是不正确的。起作用的是复合表达式的计算,例如 。简而言之,左侧的值是在右侧之前计算的。来自 JLS(强调我的):a *= b

在运行时,通过以下两种方式之一计算表达式。

如果左侧操作数表达式不是数组访问表达式, 然后:

首先,计算左操作数以生成变量。如果 此计算突然完成,然后赋值表达式 出于同样的原因突然完成;右手操作数不是 已评估,并且不会发生赋值。

否则,将保存左侧操作数的值,然后 评估右手操作数。如果此评估完成 然后,赋值表达式突然完成 同样的原因,没有发生分配。

否则,左侧变量的保存值和 右侧操作数用于执行二进制运算 由复合赋值运算符指示。如果此操作 突然完成,然后赋值表达式突然完成 出于同样的原因,并且不会发生赋值。

否则,二进制操作的结果将转换为类型 左侧变量,进行值集转换 (§5.1.13) 到适当的标准值集(不是扩展指数值 set),转换结果将存储到变量中。

在您的示例中:

a[0] *= a[0] = b[n    ];
  1. 的值计算并存储在 say 中a[0]tmp
  2. a[0] = b[n]被计算,给出 的值(并将 的值更改为b[n]a[0]b[n])
  3. tmp * a[0]计算
  4. 最后一步的结果被分配给a[0]

所以,你得到的是有效的.a[0] *= b[n]

编辑:关于从右到左评估作业的混淆:我没有发现 JLS 中使用的术语,恕我直言,这是不正确的(尽管它在 Java 教程中使用)。它被称为 right-**associative* assJLS 对赋值是这样说的:

有 12 个赋值运算符;在句法上都是 右关联(他们从右到左分组)。因此,a=b=c 表示 a=(b=c),将 c 的值分配给 b,然后分配值 从 B 到 A。

评论

0赞 Hex Crown 3/21/2018
因此,如果计算 中的左侧,计算 FIST,无论当时的值是多少,都将是缓存,那么表达式的第二部分将被运行,然后当它被解析时,它使用先前缓存的版本来完成赋值,最终存储在实际变量中?a *= baaa
0赞 ArchLinuxTux 3/21/2018
我不明白的是,为什么这被称为“从右到左”——评价。因为赋值左侧的值在右侧的值之前计算(并保存),不是吗?
0赞 Tim Biegeleisen 3/21/2018
@ArchLinuxTux JLS 似乎希望将右侧赋值视为表达式而不是实际赋值(因此保存了 LHS 值)。“真正的”任务是最后发生的。*=
0赞 Axel 3/21/2018
@ArchLinuxTux恕我直言,这在 Java 教程中是完全错误的。事实上,JLS 并没有说作业是“从右到左”评估的。它说“右联想”,这是正确的术语。
0赞 Andy Turner 3/24/2018 #4

答案中似乎还没有指出的一个关键点是,它不仅仅是一个赋值运算符,而是一个复合赋值运算符。*=

语言规范说,以下内容是等效的:

E1 op= E2
    <=>
E1 = (T) ((E1) op (E2))

哪里是类似的东西,等等。op=*=+=

因此:

a[0] *= a[0] = b[n    ];

相当于:

a[0] = a[0] * (a[0] = b[n]);

并且 在 之前被计算,因为计算顺序是从左到右a[0](a[0] = b[n])

所以:

  • 这将从(调用此”a[0]A")
  • 它从(调用此”b[n]B")
  • 它分配给Ba[0] (*)
  • 然后乘以(称之为”A * BC")
  • 然后存储回 .Ca[0]

所以是的,这相当于将 中的任何内容乘以 ,因为上面标记为 (*) 的步骤是多余的:在重新分配之前,永远不会读取那里分配的值。a[0]b[n]