ipk*_*iss 83 java operator-precedence
我正在阅读一些Java文本并获得以下代码:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
Run Code Online (Sandbox Code Playgroud)
在文中,作者没有给出明确的解释,最后一行的效果是: a[1] = 0;
我不太清楚我理解:评估是如何发生的?
Eri*_*ert 169
让我这么说清楚,因为人们总是误解这个:
子表达式的评估顺序与关联性和优先级无关.结合性和优先级确定以何种顺序的运营执行,但不以什么顺序确定的子表达式进行评估.您的问题是关于子表达式的评估顺序.
考虑A() + B() + C() * D().乘法优先于加法,加法是左关联的,所以这相当于(A() + B()) + (C() * D()) 但知道只告诉你第一次加法将在第二次加法之前发生,并且乘法将在第二次加法之前发生.它没有告诉你A(),B(),C()和D()的调用顺序!(它也没有告诉你乘法是在第一次加法之前或之后发生的.)通过将其编译为:完全可能遵守优先级和相关性规则:
d = D() // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last
Run Code Online (Sandbox Code Playgroud)
所有的优先级和关联性规则都遵循 - 第一次加法发生在第二次加法之前,乘法发生在第二次加法之前.显然,我们可以按任何顺序调用A(),B(),C()和D(),并且仍然遵守优先级和关联性规则!
我们需要一个与优先级和关联性规则无关的规则来解释子表达式的计算顺序.Java(和C#)中的相关规则是"从左到右评估子表达式".由于A()出现在C()的左侧,因此首先计算A(),而不管C()是否涉及乘法,而A()仅涉及加法.
所以现在你有足够的信息来回答你的问题.在a[b] = b = 0关联性规则中说这是a[b] = (b = 0);但这并不意味着b=0先运行!优先级规则说索引的优先级高于赋值,但这并不意味着索引器在最右边的赋值之前运行.
(更新:这个答案的早期版本在我接下来的部分中有一些小的,实际上不重要的遗漏.我还写了一篇博客文章,描述了为什么这些规则在Java和C#中是明智的:https:// ericlippert.com/2019/01/18/indexer-error-cases/)
优先级和结合只是告诉我们,零分配到b必须发生之前的分配a[b],因为零分配计算可以在索引操作分配的值.优先级和结合单独只字不提是否在a[b]评估之前或之后的b=0.
同样,这与以下内容相同:A()[B()] = C()- 我们所知道的是索引必须在赋值之前发生.我们不知道A(),B()或C()是否首先根据优先级和关联性运行.我们需要另一条规则来告诉我们.
该规则同样是"当你可以选择先做什么时,总是从左到右".然而,在这个特定场景中有一个有趣的皱纹.由null集合或超出范围索引引起的抛出异常的副作用是否被认为是赋值左侧计算的一部分,或者是赋值本身计算的一部分?Java选择后者.(当然,这是一个区别,只有在代码已经错误的情况下才有意义,因为正确的代码不会取消引用null或者首先传递错误的索引.)
那会发生什么?
a[b]是的左侧b=0,所以a[b]跑第一,从而导致a[1].但是,检查此索引操作的有效性会延迟.b=0发生了.a有效且a[1]在范围内的验证a[1]最后发生.因此,虽然在这个特定的情况下,对于那些不应该在正确的代码中出现的罕见错误情况需要考虑一些细微之处,一般情况下你可以推理:左边的事情发生在右边的事情之前.这是你正在寻找的规则.谈论优先权和相关性既令人困惑又无关紧要.
人们总是把这些东西弄错了,即使是那些应该知道更好的人.我编辑了太多的编程书,说明规则不正确,所以很多人对优先级/关联性和评估顺序之间的关系完全不正确的信念就不足为奇了 - 也就是说,实际上没有这样的关系; 他们是独立的.
如果您对此主题感兴趣,请参阅我关于该主题的文章以供进一步阅读:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
它们与C#有关,但大部分内容同样适用于Java.
Don*_*ows 32
尽管如此,Eric Lippert的精湛答案并没有得到很好的帮助,因为它正在谈论另一种语言.这是Java,其中Java语言规范是语义的权威描述.特别是,§15.26.1是相关的,因为它描述了=运算符的评估顺序(我们都知道它是右关联的,是吗?).在这个问题中将它减少到我们关心的位:
如果左侧操作数表达式是数组访问表达式(第15.13节),则需要执行许多步骤:
- 首先,评估左侧操作数数组访问表达式的数组引用子表达式.如果此评估突然完成,则赋值表达式出于同样的原因突然完成; 不评估索引子表达式(左侧操作数数组访问表达式)和右侧操作数,也不进行赋值.
- 否则,将评估左侧操作数数组访问表达式的索引子表达式.如果此评估突然完成,则赋值表达式会出于同样的原因突然完成,并且不会评估右侧操作数并且不会进行赋值.
- 否则,将评估右侧操作数.如果此评估突然完成,则赋值表达式会出于同样的原因突然完成,并且不会发生任何分配.
[...然后继续描述赋值本身的实际含义,为简洁起见,我们可以忽略它......]
简而言之,Java有一个非常严格定义的评估顺序,它在任何运算符或方法调用的参数中几乎完全是从左到右.数组赋值是更复杂的情况之一,但即便如此,它仍然是L2R.(JLS确实建议您不要编写需要这些复杂语义约束的代码,我也是如此:每个语句只需要一个赋值,就会遇到很多麻烦!)
在这个领域,C和C++肯定与Java不同:它们的语言定义使得评估顺序无意地被定义,以实现更多优化.C#显然与Java类似,但我不能很好地了解它的文献,无法指出正式的定义.(这实际上因语言而异,Ruby严格来说是L2R,就像Tcl一样 - 尽管由于这里没有相关的原因缺少赋值运算符本身 - 而且Python是L2R但R2L就赋值而言,我觉得很奇怪但是你去了.)
小智 5
a[b] = b = 0;
Run Code Online (Sandbox Code Playgroud)
1)数组索引运算符具有更高的优先级然后赋值运算符(请参阅此答案):
(a[b]) = b = 0;
Run Code Online (Sandbox Code Playgroud)
2)根据15.26.JLS的分配运算符
有12个赋值运算符; 所有这些都是语法上的右关联(他们从右到左分组).因此,a = b = c表示a =(b = c),它将c的值赋给b,然后将b的值赋给a.
(a[b]) = (b=0);
Run Code Online (Sandbox Code Playgroud)
3)根据15.7.JLS的评估顺序
Java编程语言保证运算符的操作数似乎以特定的评估顺序进行评估,即从左到右.
和
在评估右侧操作数的任何部分之前,似乎完全评估了二元运算符的左侧操作数.
所以:
a)(a[b])首先评估a[1]
b)然后(b=0)评估为0
c)(a[1] = 0)最后评估