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

发布于 2024-11-26 01:02:39 字数 184 浏览 4 评论 0 原文

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

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

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

我不是所以我确信我明白:评估是如何发生的?

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

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;

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

故笙诉离歌 2024-12-03 01:02:39

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

子表达式的求值顺序独立于结合性和优先级。关联性和优先级确定运算符的执行顺序,但确定子表达式的求值顺序。您的问题是关于子表达式的计算顺序。

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

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

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

所以现在您有足够的信息来回答您的问题。在 a[b] = b = 0 中,结合性规则表示这是 a[b] = (b = 0); 但这并不意味着 code>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() 首先运行基于优先级和关联性。我们需要另一条规则来告诉我们这一点。

规则再次是,“当你可以选择先做什么时,总是从左到右”。然而,在这个特定场景中存在一个有趣的问题。 由空集合或超出范围索引引起的抛出异常的副作用是被视为赋值左侧计算的一部分,还是赋值本身计算的一部分? 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。

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

Order of evaluation of subexpressions is independent of both associativity and precedence. Associativity and precedence determine in what order the operators are executed but do not determine in what order the subexpressions are evaluated. Your question is about the order in which subexpressions are 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 associativity by compiling this as:

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 any order and still obey the rules of precedence and associativity!

We need a rule unrelated to 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.

So now you have enough information to answer your question. In a[b] = b = 0 the rules of associativity say that this is a[b] = (b = 0); but that does not mean that the b=0 runs 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.

(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/)

Precedence and associativity only tell us that the assignment of zero to b must happen before the 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 before or after the 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.

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.)

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 validity of this indexing operation is delayed.
  • Then the b=0 happens.
  • Then the verification that a is valid and a[1] is in range happens
  • The assignment of the value to a[1] happens last.

So, though in this specific case 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 many programming 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/

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

荒芜了季节 2024-12-03 01:02:39

埃里克·利珀特(Eric Lippert)的精彩回答仍然没有多大帮助,因为它谈论的是另一种语言。这就是Java,其中Java语言规范是语义的明确描述。特别是 §15.26.1是相关的,因为它描述了 = 运算符的求值顺序(我们都知道它是右关联的,是吗?)。将其简化为我们在这个问题中关心的部分:

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

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

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

简而言之,Java 有一个非常严格定义的 评估顺序 在参数中几乎完全是从左到右任何运营商或方法调用。数组赋值是更复杂的情况之一,但即使如此,它仍然是 L2R。 (JLS 确实建议您不要编写需要此类复杂语义约束的代码,我也是如此:每个语句仅进行一次赋值就可能陷入足够多的麻烦!)

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

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.1 is 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:

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.

[… 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 order that 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!)

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 se for reasons not relevant here — and Python is L2R but R2L in respect of assignment, which I find odd but there you go.)

一个人的夜不怕黑 2024-12-03 01:02:39
a[b] = b = 0;

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

(a[b]) = b = 0;

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

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

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

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

Java 编程语言保证运算符的操作数按特定的求值顺序(即从左到右)求值。

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

因此:

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

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

c) (a[1] = 0) 最后计算

a[b] = b = 0;

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

(a[b]) = b = 0;

2) According to 15.26. Assignment Operators of 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.

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

3) According to 15.7. Evaluation Order of 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.

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]

b) then (b=0) evaluated to 0

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

友谊不毕业 2024-12-03 01:02:39

您的代码相当于:

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

它解释了结果。

Your code is equivalent to:

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

which explains the result.

长发绾君心 2024-12-03 01:02:39

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

一般经验法则:

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

这是一个很好的例子:

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

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

答案:应用优先级和结合性规则

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

100/10*2

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

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

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

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

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

=3+20-13

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

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

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

10 是编译时的正确输出

同样,在解决这些问题时,有一个优先顺序规则和关联性表非常重要,例如 < a href="http://introcs.cs.princeton.edu/java/11precedence/" rel="nofollow noreferrer">http://introcs.cs.princeton.edu/java/11precedence/

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/

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:

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*2
=100/10
=10*2
=20

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

=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
=23
=23-13
=10

10 is the correct output when compiled

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/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文