Java中评估顺序的规则是什么?

2019年1月12日 45点热度 0条评论

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

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

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

我不确定自己是否理解:评估是如何发生的?

解决方案如下:

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

子表达式的求值顺序与关联性和优先级无关。关联性和优先级确定操作符的执行顺序,但是不确定确定子表达式的计算顺序。您的问题与子表达式的计算顺序有关。

考虑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);,但这并不意味着
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。