Java 中求值顺序的规则是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6800590/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 17:20:00  来源:igfitidea点击:

What are the rules for evaluation order in Java?

javaoperator-precedence

提问by ipkiss

I am reading some Java text and got the following code:

我正在阅读一些 Java 文本并得到以下代码:

int[] a = {4,4};
int b = 1;
a[b] = b = 0;

In the text, the author did not give a clear explanation and the effect of the last line is: a[1] = 0;

文中,作者没有给出明确的解释,最后一行的效果是: a[1] = 0;

I am not so sure that I understand: how did the evaluation happen?

我不太确定我是否理解:评估是如何进行的?

回答by Eric Lippert

Let me say this very clearly, because people misunderstand this all the time:

让我说的很清楚,因为人们一直误解这一点:

Order of evaluation of subexpressions is independentof both associativity and precedence. Associativity and precedence determine in what order the operatorsare executed but do notdetermine in what order the subexpressionsare evaluated. Your question is about the order in which subexpressionsare evaluated.

子表达式的求值顺序与结合性和优先级无关。结合性和优先级确定以什么顺序运营商执行,但以什么顺序确定的子表达式进行评估。您的问题是关于评估子表达式的顺序。

Consider A() + B() + C() * D(). Multiplication is higher precedence than addition, and addition is left-associative, so this is equivalent to (A() + B()) + (C() * D())But knowing that only tells you that the first addition will happen before the second addition, and that the multiplication will happen before the second addition. It does not tell you in what order A(), B(), C() and D() will be called!(It also does not tell you whether the multiplication happens before or after the first addition.) It would be perfectly possible to obey the rules of precedence and associativityby compiling this as:

考虑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

All the rules of precedence and associativity are followed there -- the first addition happens before the second addition, and the multiplication happens before the second addition. Clearly we can do the calls to A(), B(), C() and D() in anyorder and still obey the rules of precedence and associativity!

遵循所有优先级和结合性规则——第一次加法发生在第二次加法之前,乘法发生在第二次加法之前。显然,我们可以以任何顺序调用 A()、B()、C() 和 D(),并且仍然遵守优先级和结合性规则!

We need a rule unrelatedto the rules of precedence and associativity to explain the order in which the subexpressions are evaluated. The relevant rule in Java (and C#) is "subexpressions are evaluated left to right".Since A() appears to the left of C(), A() is evaluated first, regardless of the fact that C() is involved in a multiplication and A() is involved only in an addition.

我们需要一个优先级和结合性规则无关的规则来解释子表达式的求值顺序。Java(和 C#)中的相关规则是“从左到右计算子表达式”。由于 A() 出现在 C() 的左侧,因此首先计算 A(),而不管 C() 涉及乘法而 A() 仅涉及加法的事实。

So now you have enough information to answer your question. In a[b] = b = 0the rules of associativity say that this is a[b] = (b = 0);but that does not mean that the b=0runs first! The rules of precedence say that indexing is higher precedence than assignment, but that does not mean that the indexer runs before the rightmost assignment.

所以现在你有足够的信息来回答你的问题。在a[b] = b = 0结合性规则中说这是a[b] = (b = 0);但那并不意味着b=0首先运行!优先级规则说索引的优先级高于赋值,但这并不意味着索引器在最右边的赋值之前运行

(UPDATE: An earlier version of this answer had some small and practically unimportant omissions in the section which follows which I have corrected. I've also written a blog article describing why these rules are sensible in Java and C# here: https://ericlippert.com/2019/01/18/indexer-error-cases/)

(更新:此答案的早期版本在接下来的部分中有一些小的且实际上不重要的遗漏,我已更正。我还写了一篇博客文章,描述了为什么这些规则在 Java 和 C# 中是合理的:https:// ericlippert.com/2019/01/18/indexer-error-cases/)

Precedence and associativity only tell us that the assignment of zeroto bmust happen beforethe assignment to a[b], because the assignment of zero computes the value that is assigned in the indexing operation. Precedence and associativity alone say nothing about whether the a[b]is evaluated beforeor afterthe b=0.

优先级和结合只是告诉我们,零分配b必须发生之前的分配a[b],因为零次计算可以在索引操作分配价值的分配。优先级和是否关联性单独甭a[b]评估之前之后b=0

Again, this is just the same as: A()[B()] = C()-- All we know is that the indexing has to happen before the assignment. We don't know whether A(), B(), or C() runs first based on precedence and associativity. We need another rule to tell us that.

同样,这与以下内容相同:A()[B()] = C()-- 我们所知道的是索引必须在分配之前进行。我们不知道是 A()、B() 还是 C()根据优先级和关联性首先运行。我们需要另一条规则来告诉我们这一点。

The rule is, again, "when you have a choice about what to do first, always go left to right". However, there is an interesting wrinkle in this specific scenario. Is the side effect of a thrown exception caused by a null collection or out-of-range index considered part of the computation of the left side of the assignment, or part of the computation of the assignment itself?Java chooses the latter. (Of course, this is a distinction that only matters if the code is already wrong, because correct code does not dereference null or pass a bad index in the first place.)

同样,规则是“当您可以选择先做什么时,请始终从左到右”。然而,在这个特定场景中有一个有趣的问题。由空集合或超出范围的索引引起的抛出异常的副作用是被视为赋值左侧计算的一部分,还是赋值本身计算的一部分?Java 选择了后者。(当然,这是一个区别,仅当代码已经错误时才重要,因为正确的代码首先不会取消引用 null 或传递错误的索引。)

So what happens?

那么会发生什么?

  • The a[b]is to the left of the b=0, so the a[b]runs first, resulting in a[1]. However, checking the validityof this indexing operation is delayed.
  • Then the b=0happens.
  • Then the verification that ais valid and a[1]is in range happens
  • The assignment of the value to a[1]happens last.
  • a[b]是的左侧b=0,所以a[b]第一,从而导致a[1]。但是,会延迟检查此索引操作的有效性
  • 然后b=0发生了。
  • 然后发生a有效且a[1]在范围内的验证
  • 值的分配a[1]发生在最后。

So, though in this specificcase there are some subtleties to consider for those rare error cases that should not be occurring in correct code in the first place, in general you can reason: things to the left happen before things to the right. That's the rule you're looking for. Talk of precedence and associativity is both confusing and irrelevant.

因此,尽管在这种特定情况下,对于那些一开始不应该在正确代码中发生的罕见错误情况,需要考虑一些微妙之处,但通常您可以推理:左边的事情发生在右边的事情之前。这就是你要找的规则。优先级和关联性的讨论既令人困惑又无关紧要。

People get this stuff wrong all the time, even people who should know better. I have edited far too manyprogramming books that stated the rules incorrectly, so it is no surprise that lots of people have completely incorrect beliefs about the relationship between precedence/associativity, and evaluation order -- namely, that in reality there is no such relationship; they are independent.

人们总是把这些东西弄错,即使是那些应该知道得更好的人。我编辑了太多错误说明规则的编程书籍,因此很多人对优先级/结合性和评估顺序之间的关系有完全错误的信念也就不足为奇了——也就是说,实际上没有这种关系; 他们是独立的。

If this topic interests you, see my articles on the subject for further reading:

如果您对这个主题感兴趣,请参阅我关于该主题的文章以进一步阅读:

http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/

http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/

They are about C#, but most of this stuff applies equally well to Java.

它们是关于 C# 的,但其中大部分内容同样适用于 Java。

回答by Donal Fellows

Eric Lippert's masterful answer is nonetheless not properly helpful because it is talking about a different language. This is Java, where the Java Language Specification is the definitive description of the semantics. In particular, §15.26.1is relevant because that describes the evaluation order for the =operator (we all know that it is right-associative, yes?). Cutting it down a little to the bits that we care about in this question:

埃里克·利珀特 (Eric Lippert) 的巧妙回答仍然没有适当的帮助,因为它在谈论一种不同的语言。这就是 Java,其中 Java 语言规范是语义的权威描述。特别是,§15.26.1是相关的,因为它描述了=运算符的评估顺序(我们都知道它是右结合的,是吗?)。把它减少到我们在这个问题中关心的部分:

If the left-hand operand expression is an array access expression (§15.13), then many steps are required:

  • First, the array reference subexpression of the left-hand operand array access expression is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason; the index subexpression (of the left-hand operand array access expression) and the right-hand operand are not evaluated and no assignment occurs.
  • Otherwise, the index subexpression of the left-hand operand array access expression is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and the right-hand operand is not evaluated and no assignment occurs.
  • Otherwise, the right-hand operand is evaluated. If this evaluation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.

如果左侧操作数表达式是数组访问表达式(第15.13 节),则需要许多步骤:

  • 首先,评估左侧操作数数组访问表达式的数组引用子表达式。如果这个评估突然完成,那么赋值表达式也会因为同样的原因突然完成;索引子表达式(左侧操作数数组访问表达式的)和右侧操作数不求值,也不进行赋值。
  • 否则,将评估左侧操作数数组访问表达式的索引子表达式。如果此评估突然完成,则赋值表达式会出于相同的原因突然完成,并且不会评估右侧操作数,也不会发生赋值。
  • 否则,评估右侧操作数。如果此评估突然完成,则赋值表达式会出于相同的原因突然完成,并且不会发生赋值。

[… it then goes on to describe the actual meaning of the assignment itself, which we can ignore here for brevity …]

[……然后继续描述赋值本身的实际含义,为了简洁起见,我们可以在这里忽略……]

In short, Java has a very closely defined evaluation orderthat is pretty much exactly left-to-right within the arguments to any operator or method call. Array assignments are one of the more complex cases, but even there it's still L2R. (The JLS does recommend that you don't write code that needs these sorts of complex semantic constraints, and so do I: you can get into more than enough trouble with just one assignment per statement!)

简而言之,Java 有一个非常严格定义的求值顺序,它在任何运算符或方法调用的参数中几乎完全是从左到右。数组分配是更复杂的情况之一,但即便如此,它仍然是 L2R。(JLS 确实建议您不要编写需要这些复杂语义约束的代码,我也是如此:您可能会因为每个语句只进行一个赋值而遇到足够多的麻烦!)

C and C++ are definitely different to Java in this area: their language definitions leave evaluation order undefined deliberately to enable more optimizations. C# is like Java apparently, but I don't know its literature well enough to be able to point to the formal definition. (This really varies by language though, Ruby is strictly L2R, as is Tcl — though that lacks an assignment operator per sefor reasons not relevant here — and Python is L2R but R2L in respect of assignment, which I find odd but there you go.)

C 和 C++ 在这方面与 Java 完全不同:它们的语言定义故意未定义评估顺序以启用更多优化。C# 显然就像 Java,但我不太了解它的文献,无法指出正式的定义。(虽然这确实因语言而异,Ruby 是严格的 L2R,Tcl 也是如此——尽管由于这里不相关的原因,它本身缺少赋值运算符——而 Python 是L2R,但在赋值方面R2L,我觉得这很奇怪,但你去了.)

回答by bup

a[b] = b = 0;

1) array indexing operator has higher precedence then assignment operator (see this answer):

1) 数组索引运算符的优先级高于赋值运算符(请参阅此答案):

(a[b]) = b = 0;

2) According to 15.26. Assignment Operators of JLS

2) 根据 15.26。JLS 的赋值运算符

There are 12 assignment operators; all are syntactically right-associative (they group right-to-left). Thus, a=b=c means a=(b=c), which assigns the value of c to b and then assigns the value of b to a.

有 12 个赋值运算符;所有的语法都是右关联的(它们从右到左分组)。因此,a=b=c 表示 a=(b=c),即先将 c 的值赋给 b,然后再将 b 的值赋给 a。

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

3) According to 15.7. Evaluation Order of JLS

3) 根据 15.7。JLS的评价顺序

The Java programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.

Java 编程语言保证运算符的操作数以特定的求值顺序进行求值,即从左到右。

and

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.

二元运算符的左侧操作数似乎在评估右侧操作数的任何部分之前已完全评估。

So:

所以:

a) (a[b])evaluated first to a[1]

a)(a[b])首先评估a[1]

b) then (b=0)evaluated to 0

b) 然后(b=0)评估为0

c) (a[1] = 0)evaluated last

c)(a[1] = 0)最后评估

回答by Jér?me Verstrynge

Your code is equivalent to:

您的代码相当于:

int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;

which explains the result.

这解释了结果。

回答by Mark Burleigh

Consider another more in-depth example below.

考虑下面另一个更深入的例子。

As a General Rule of Thumb:

作为一般经验法则:

It's best to have a table of the Order of Precedence Rules and Associativity available to read when solving these questions e.g. http://introcs.cs.princeton.edu/java/11precedence/

在解决这些问题时,最好有一张优先规则顺序和关联性的表格可供阅读,例如http://introcs.cs.princeton.edu/java/11precedence/

Here is a good example:

这是一个很好的例子:

System.out.println(3+100/10*2-13);

Question: What's the Output of the above Line?

问题:上面一行的输出是什么?

Answer: Apply the Rules of Precedence and Associativity

答案:应用优先级和关联性规则

Step 1: According to rules of precedence: / and * operators take priority over + - operators. Therefore the starting point to execute this equation will the narrowed to:

步骤1:根据优先级规则: / 和 * 运算符优先于 + - 运算符。因此,执行此等式的起点将缩小为:

100/10*2

Step 2: According to the rules and precedence: / and * are equal in precedence.

第二步:根据规则和优先级:/和*的优先级相等。

As / and * operators are equal in precedence, we need to look at the associativity between those operators.

由于 / 和 * 运算符的优先级相等,我们需要查看这些运算符之间的关联性。

According to the ASSOCIATIVITY RULES of these two particular operators, we start executing the equation from the LEFT TO RIGHT i.e. 100/10 gets executed first:

根据这两个特定运算符的关联规则,我们从左到右开始执行方程,即首先执行 100/10:

100/10*2
=100/10
=10*2
=20

Step 3: The equation is now in the following state of execution:

第 3 步:方程现在处于以下执行状态:

=3+20-13

According to the rules and precedence: + and - are equal in precedence.

根据规则和优先级:+和-的优先级相等。

We now need to look at the associativity between the operators + and - operators. According to the associativity of these two particular operators, we start executing the equation from the LEFT to RIGHT i.e. 3+20 gets executed first:

我们现在需要查看运算符 + 和 - 运算符之间的关联性。根据这两个特定运算符的结合性,我们从左到右开始执行方程,即首先执行 3+20:

=3+20
=23
=23-13
=10

10 is the correct output when compiled

10 是编译时的正确输出

Again, it is important to have a table of the Order of Precedence Rules and Associativity with you when solving these questions e.g. http://introcs.cs.princeton.edu/java/11precedence/

同样,在解决这些问题时,请务必准备好优先规则和关联性顺序表,例如http://introcs.cs.princeton.edu/java/11precedence/