Java中的评估顺序是什么?

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

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

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

我不太清楚,我明白:评估是如何发生的?

让我这么说很清楚,因为人们总是误解这个:

子expression的评价顺序与结合性和优先性无关 。 关联性和优先级决定了操作符执行的顺序,但决定子expression式的评估顺序。 你的问题是关于子expression式评估的顺序。

考虑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()进行调用,并且仍然遵从优先级和相关性的规则!

我们需要一个优先级和关联性规则无关的规则来解释子expression式的评估顺序。 Java(和C#)中的相关规则是“子expression式从左到右评估”。 由于A()出现在C()的左边,所以A()首先被计算, 而不pipeC()是否涉及乘法,而A()只涉及到加法。

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

优先级和关联性规则强加以下限制:

  • 索引器操作必须在与左侧分配操作相关的操作之前运行
  • 与右侧分配操作相关的操作必须在与左侧分配操作关联的操作之前运行。

优先级和关联性只告诉我们,在赋值给a[b] 之前必须发生从0b的赋值。 优先级和关联性没有说明a[b]是在b=0 之前还是之后被评估的。

再一次,这与A()[B()] = C() – 我们所知道的是索引必须在赋值之前发生。 我们不知道A(),B()或C()是否先于优先级和关联性运行 。 我们需要另一条规则来告诉我们。

规则又是“当你有select先做什么的时候,总是从左到右”: a[b]b=0的左边,所以a[b] 运行,结果是在a[1] 。 然后b=0发生,然后赋值给a[1]最后发生。

左边的事情发生在右边的事情之前。 这是你正在寻找的规则。 优先级和关联性的讨论既混乱又无关紧要。

人们总是弄错这个东西,即使是那些应该知道得更好的人。 我编辑了太多编程书籍,错误地规定了规则,所以很多人对于优先/关联性和评价顺序之间的关系完全不正确的信念并不奇怪,即现实中没有这种关系; 他们是独立的。

如果这个话题引起了你的兴趣,请参阅我关于这个主题的文章,

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

他们是关于C#的,但是这些东西大部分同样适用于Java。

埃里克·利波特(Eric Lippert)的杰出的回答并不是很有帮助,因为它正在谈论一种不同的语言。 这是Java语言,其中Java语言规范是对语义的明确描述。 具体而言, §15.26.1是相关的,因为它描述了=运算符的评估顺序(我们都知道这是正确的联合,是的?)。 在这个问题上把它们缩小一点,让我们在乎:

如果左边的操作数expression式是一个数组访问expression式(第15.13节 ),那么需要很多步骤:

  • 首先,评估左侧操作数数组访问expression式的数组引用子expression式。 如果这个评估突然完成,那么由于相同的原因,赋值expression式突然完成; (左侧操作数数组访问expression式)的索引子expression式和右侧操作数不计算在内,并且不进行分配。
  • 否则,评估左侧操作数数组访问expression式的索引子expression式。 如果此评估突然完成,则由于相同的原因,赋值expression式会突然完成,右侧的操作数不会被计算,也不会进行赋值。
  • 否则,评估右侧的操作数。 如果此评估突然完成,则由于相同的原因,分配expression式会突然完成,并且不会发生分配。

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

简而言之,Java有一个非常接近定义的评估顺序 ,在任何操作符或方法调用的参数内都几乎是从左到右。 数组赋值是更复杂的情况之一,但即使在那里,它仍然是L2R。 (JLSbuild议您不要编写那些需要这些复杂的语义约束的代码,我也是如此:只要每个语句只有一个赋值,您就可以得到足够多的麻烦!

C和C ++在这方面与Java显然是不同的:它们的语言定义没有定义评估顺序,故意进行更多的优化。 C#很像Java,但我不清楚它的文献是否足以指出正式的定义。 (虽然这真的是因为语言的不同,Ruby是严格的L2R,Tcl也是如此 – 尽pipe由于这里没有相关的原因,本身没有赋值运算符 – 而Python是L2R,但在赋值方面是R2L ,我觉得这很奇怪, 。)

 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)最后评估

您的代码相当于:

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

这解释了结果。

这里是另一个例子来概述Java中的优先顺序

考虑下面的示例代码:

 class A{ public static void main(String[] args){ int x = 20; x =x+(x=5); System.out.println(x); }} 

问题:x的输出是什么? 在继续下面的答案之前尝试解决这个问题。

回答:

第1步:编译器将20分配给variablesx – 这很重要,所有的variables都是先定义的

 int x = 20; 

第二步:编译器首先应用优先顺序规则,即括号。

 (x=5); 

现在我们有:

 x =20+(x=5); 

其结果是:

 x =20+5; 

所以输出是25:

 System.out.println(x); 

结论:

你必须这样做:

1所有variables都是首先在expression式中定义的。

2你知道运算符的优先顺序和关联性。

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

作为一般的经验法则:

解决这些问题时最好有一张优先顺序和关联性顺序表,例如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是编译时的正确输出

同样,在解决这些问题时,有一个优先顺序和关联性顺序表是非常重要的,例如http://introcs.cs.princeton.edu/java/11precedence/

 public class TestClass{   public static void main(String args[] ){      int i = 0 ;      int[] iA = {10, 20} ;      iA[i] = i = 30 ; System.out.println(""+ iA[ 0 ] + " " + iA[ 1 ] + "  "+i) ;    } } 

它将打印30 20 30

声明iA [i] = i = 30; 将被处理如下:

iA [i] = i = 30; => iA [0] = i = 30; => i = 30; iA [0] = i; => iA [0] = 30;

这是JLS在这方面所说的:

1首先评估左手操作数
2在操作之前评估操作数
3评估尊重括号和优先权
4个参数列表从左到右进行评估

对于数组:首先,维度expression式被评估,从左到右。 如果任何expression式评估突然完成,则不评估其右侧的expression式。