【问题标题】:Execution order of f1() + f2()*f3() expression and operator precedence in JLSJLS中f1() + f2()*f3()表达式的执行顺序和运算符优先级
【发布时间】:2019-12-21 09:49:17
【问题描述】:

给定一个带有 3 个方法调用的表达式 f1() + f2()*f3(),java 评估(操作数)首先进行加法运算:

int result = f1() + f2()*f3();

f1 working
f2 working
f3 working

我(错误地)期望首先调用f2(),然后调用f3(),最后调用f1()。因为乘法应该在加法之前计算。

所以,我不明白 JLS 在这里 - 我错过了什么?

15.7.3。 评估方面括号和优先级

Java 编程语言尊重求值顺序 用括号显式表示,用运算符隐式表示 优先级。

在这个例子中,运算符优先级是如何被尊重的?

JLS 在15.7.5. Evaluation Order for Other Expressions 中提到了一些例外情况(方法调用表达式(§15.12.4),方法引用表达式(§15.13.3)),但我无法将任何这些例外应用于我的示例。

我知道 JLS 保证二元运算的操作数是从左到右计算的。 但是在我的示例中,为了理解方法调用顺序,有必要了解哪个操作(以及它的两个操作数!)首先被考虑。我错了吗?为什么?

更多示例:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

自豪地生产:

f1 working
f2 working
f3 working
f4 working
f5 working
f6 working
f7 working
f8 working

这是 100% 从左到右,完全不考虑运算符的优先级。

int result = f1() + f2() + f3()*f4();

产生:

f1 working
f2 working
f3 working
f4 working

这里编译器有两个选择:

  1. f1()+f2() - 先加法
  2. f3()*f4() - 先乘法

但是“评估从左到右的规则”在这里很有魅力!

link 暗示在此类示例中,方法调用始终从左到右进行评估,而不管任何括号和关联规则。

【问题讨论】:

  • 它按顺序评估函数。它根据 PEMDAS 应用数学运算符。
  • 第一步,找出表达式中的值的方法 - 方法称为 LTR。然后找出表达式的值 - 评估尊重运算符的优先顺序。
  • 您应该阅读 15.7.1 和 15.7.2。 15.7.3 只是意味着f1()+f1() 不会被2*f1() 替换,3.0*x/4.0 不会被(3.0/4.0)*x 替换,等等......

标签: java operator-precedence evaluation jls expression-evaluation


【解决方案1】:

因为要在加法之前计算乘法。

正如您的意思,这不是对乘法优先于加法的合理描述,也不是对第 15.7.3 节的效果的合理描述。优先级表示你的表达方式

f1() + f2()*f3()

评估结果与

相同
f1() + (f2() * f3())

,相对于

(f1() + f2()) * f3()

无论操作数的性质如何,优先级不会说明首先评估 + 运算符的哪个操作数。相反,它说明每个操作数 是什么。这确实会影响评估顺序,因为它遵循优先规则,即原始表达式中的乘法结果必须在加法结果之前计算,但这并不直接涉及两个因素的值何时的问题相对于彼此或其他子表达式进行评估。

与您引用的规则不同的规则负责计算 + 或 * 运算符的两个操作数的顺序 - 始终是左操作数第一个,右操作数第二个。

在这个例子中,运算符优先级是如何被尊重的?

该示例的输出并未传达有关优先级(正确理解)是否得到尊重的信息。对于上述两个带括号的备选方案,将观察到相同的输出,优先级是关于这两个备选方案中的哪一个与原始表达式等效。

在操作顺序的意义上,优先级(仅)表示加法的右侧操作数是f2()*f3(),这意味着必须先计算乘积,然后才能计算总和。 JLS 15.7.3 并没有说更多或不同的东西。该子表达式是一个乘法,与其相对于加法运算的另一个操作数的求值顺序无关。

我知道 JLS 保证二元运算的操作数是从左到右计算的。但是在我的例子中是为了 了解方法调用顺序有必要了解 考虑哪个操作(及其两个操作数!) 首先。我错了吗?为什么?

是的,你错了,尽管有 JLS,你的例子提供了很好的证据。

问题似乎是“及其两个操作数分别”位。您的想法似乎大致是Java应该查看表达式,找到所有乘法运算并完全评估它们,包括它们的操作数,然后才返回并对加法运算执行相同的操作。但这并不要求遵守优先级,而且它与加法运算的从左到右的求值顺序不一致,它会使 Java 代码更难人类编写或阅读。

让我们考虑你的更长的例子:

int result = f1() + f2()*f3() + f4() + f5()*f6() + (f7()+f8());

评估过程如下:

  • f1() 先求值,因为它是第一个操作的左操作数,并且该操作的操作数必须从左到右求值
  • f2() 接下来会被求值,因为它是加法的右操作数的一部分(轮到它求值),特别是乘法的左操作数
  • f3() 接下来被求值,因为它所属的乘法子表达式正在被求值,并且它是该乘法的右操作数。 优先级影响此处的操作顺序,通过确定它实际上是正在评估的产品。如果不是这样,那么在计算 f3() 之前,f2() 的结果将被添加到 f1() 的结果中。
  • 根据乘法优先于加法计算f2() 和f3() 的乘积。
  • 计算(先前评估的)f1() 和f2()*f3() 的总和。这是由于* 优先于+ 的组合已经讨论过,以及+ 的从左到右的关联性,它确定和是下一个加法运算的左操作数,因此必须是在该操作的右操作数之前求值。
  • f4() 被求值,因为它是此时求值的加法运算的右操作数。
  • 计算第二个总和。这又是因为 + 的从左到右的关联性,以及 next 加法运算的从左到右的评估顺序。
  • f5() 然后f6() 然后评估他们的产品。这与f2()*f3() 的情况完全类似,因此是运算符优先级的另一个练习。
  • f5()*f6() 的结果,作为加法运算的右侧操作数,被添加到左侧操作数。这又类似于前面的步骤。
  • f7() 已评估。
  • 由于括号,接下来会评估 f8(),然后将其与先前确定的评估 f7() 的结果相加,然后 em> result 添加到前面的结果中以完成计算。如果没有括号,顺序会有所不同:f7() 的结果将添加到前面的结果中,然后 f8() 计算,然后添加该结果。

【讨论】:

  • 我明白你在说什么(并且在很大程度上同意它),但 OP 的规范引用确实是关于在加法之前评估乘法的事实,所以你不能真的说这是“不是对它的合理描述”。
  • @ruakh,您所指的评论不响应规范引用,而是响应 OP 自己的(错误)特征,即优先级在其代码中的含义。事实是优先级和括号不影响OP感兴趣的意义上的评估顺序。他们按照我在这里描述的方式进行操作。
  • 当然,但是“乘法应在加法之前进行评估”正是规范在这里所说的(部分)。这只是 OP 的后续解释,+ 的右侧应该在左侧之前评估(因为后者有 *),这是错误的。
  • 我不同意,@ruakh,这就是重点。规范没有这么说,尽管它的措辞容易受到这样的误解。
  • 我已经大大扩展了这个答案以(我希望)澄清这些观点。
【解决方案2】:

JLS §15.17.2. Evaluate Operands before Operation:

Java 编程语言保证每个操作数 运算符(除了条件运算符 &&、|| 和 ?:)似乎 在
操作本身的任何部分之前进行全面评估 执行。

因此,首先评估f1()、f2() 和f3()(从左到右)。然后,应用运算符优先级。

毕竟,你可以观察字节码中的执行顺序,在我的例子中是:

[...]
INVOKESTATIC Main.f1 ()I
INVOKESTATIC Main.f2 ()I
INVOKESTATIC Main.f3 ()I
IMUL
IADD

下面是表达式树的样子:

   +
  / \
 /   \
f1    *
     / \
    /   \
   f2   f3

首先计算叶子,然后在树本身中编码运算符优先级。

【讨论】:

    【解决方案3】:

    在discussion这个discussion中找到了我认为最有用的提示

    JLS 对此不太清楚 - 关于评估的第 15.7 节仅讨论单个操作的操作数,留下了不清楚的现实生活中的复杂案例,其中包含许多操作组合。

    【讨论】:

      【解决方案4】:

      您在 15.7 下缺少文本:

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

      表达式的操作数的值在表达式本身之前计算。在您的具体情况下,最外面的表达式是f1() + [f2() * f3()]。我们必须首先评估f1(),然后我们必须评估f2() * f3()。其中,首先计算 f2(),然后是 f3(),然后是乘法(返回 + 的操作数的值)。

      【讨论】:

      • 您能解释一下如何协调 §15.7.3 和 §15.7 吗?
      • 操作数求值顺序明确。问题在于(二进制)运算顺序(加法运算或乘法运算应该是第一位的 - 然后操作数评估规则让一切变得清晰)。
      • @CodeComplete 你的“附言”仍然没有抓住重点;您可能会对编译时代码分析和运行时评估感到困惑。将表达式绘制为树然后以深度优先顺序遍历它可能会有所帮助(其中叶子是 f?() 表达式)。
      猜你喜欢
      • 1970-01-01
      • 2020-06-23
      • 1970-01-01
      • 2016-12-31
      • 2011-07-24
      • 2021-10-04
      • 2020-12-03
      • 2017-10-25
      • 2016-08-20
      相关资源
      最近更新 更多