Java中前缀和后缀++操作符的区别



关于这一点有一些问题(如Java:自增/自减运算符的前缀/后缀?),但我不是询问后缀和前缀++运算符之间的一般区别(我知道那部分),而是关于它们在Java规范级别之间的基本区别。

具体来说,前缀和后缀++操作符操作符优先级(可能在javac将命令转换为字节码的方式或JVM运行该字节码的方式上)之间有任何区别吗?

例如,下面的代码是否必须运行相同(在每个JVM中):
for (int i = 0; i < X; i++) { ... }

for (int i = 0; i < X; ++i) { ... }

JLS中是否有任何东西定义这两个语句将在每个平台,Java编译器,JVM等上以完全相同的方式运行,或者这两个语句可能(甚至理论上)将以不同的方式运行?

是的,它将运行相同。它将编译成相同的字节码,因此JVM将不会注意到任何JVM的差异。

你可以自己检查:

public class TestIncrement {
    public void testPost(int X) {
        for (int i = 0; i < X; i++) {
            System.out.println(i);
        }
    }
    public void testPre(int X) {
        for (int i = 0; i < X; ++i) {
            System.out.println(i);
        }
    }
}

使用JavaC编译器以相同的方式编译这两个方法:

public void testPost(int);
Code:
   0: iconst_0
   1: istore_2
   2: iload_2
   3: iload_1
   4: if_icmpge     20
   7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
  10: iload_2
  11: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
  14: iinc          2, 1
  17: goto          2
  20: return

或ECJ编译器:

public void testPost(int);
Code:
  0: iconst_0
  1: istore_2
  2: goto          15
  5: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
  8: iload_2
  9: invokevirtual #22                 // Method java/io/PrintStream.println:(I)V
 12: iinc          2, 1
 15: iload_2
 16: iload_1
 17: if_icmplt     5
 20: return

不同的编译器生成不同的字节码,但在这两种情况下,两个方法的字节码是相同的。

规范的相关部分是:

15.15.1。前缀递增运算符++

[…]

在运行时,如果操作数表达式的求值突然完成,则前缀自增表达式出于同样的原因突然完成,并且不发生自增操作。否则,将值1添加到变量的值中,并将总和存储回变量中。[…]
前缀增量表达式的值是存储新值后变量的值。

15.14.2。后置自增运算符++

[…]

在运行时,如果操作数表达式的求值突然完成,则后缀自增表达式出于同样的原因突然完成,并且不发生自增操作。否则,将值1添加到变量的值中,并将总和存储回变量中。[…]
后缀自增表达式的值是在存储新值之前变量的值。

因此,唯一的区别是在表达式上下文中使用时的结果值。当在for循环的update子句上下文中使用时,相关部分为:

14.14.1.2。for语句

的迭代

[…]首先,如果存在ForUpdate部分,则按从左到右的顺序求值表达式;它们的值(如果有)将被丢弃。如果任何表达式的求值因为某种原因突然完成,那么for语句也因为同样的原因突然完成;对于突然完成的语句表达式右边的任何ForUpdate语句表达式都不求值。

从字面上理解在代码中会有差异,因为这些表达式产生不同的结果,然后被丢弃。然而,这种差异在于不可观察的行为,因此,代码通常被编译为首先不产生该值,因此没有差异。

所以答案是,代码可能有差异,例如,在幼稚编译时,但是可观察的行为保证是相同的。

Java的一个主要特点是代码将在每个JVM上运行相同。虽然编译器可以为相同的源代码生成不同的字节码,但它们需要遵循一组规则,就像不同的jvm一样,以确保程序正确运行。

没有未定义的或平台依赖的行为,例如c。

来自Oracle文档:

增量表达式在每次循环迭代后调用;对于这个表达式,递增或递减值是完全可以接受的。

摘自Oracle的JLS文档:

首先,如果ForUpdate部分存在,则表达式按从左到右的顺序求值;如果有,则丢弃它们的值。

由于在之后调用,迭代和返回值将被丢弃,因此两个选项之间不应该有任何区别。也许字节码不完全相同,但应该是相等的。

这里已经有了很好的答案,但我想尝试从另一个角度来回答这个问题。我将只看for循环必须如何工作,在任何具有与Java相同类型的for循环的语言中。

它有四个部分:

  • 初始化部分
  • 条件部分
  • 循环变量修改部分
  • 要循环通过
  • 的身体部分

任何编译器都需要单独处理它们,它们不能通过以任何方式将它们连接起来进行优化。(至少我是这么认为的)每个部分都必须被视为某种孤立的实体。在本讨论中,我们只对循环变量修改部分感兴趣。

因为它是一个孤立的实体,所以使用i++还是--i都无关紧要。没有任何Java编译器,任何其他语言的编译器或任何语言解析器可以在这方面以不同的方式处理for循环。

这意味着答案必须是:是的,它们必须以相同的方式运行