Java 中前缀和后缀 ++ 运算符的区别

Difference between prefix and postfix ++ operators in Java

关于这个有几个问题(比如 Java: Prefix/postfix of increment/decrement operators?),但我 不是 询问后缀和前缀 ++ 运算符之间的一般区别(我知道那部分),但关于 它们在 Java 规格级别 .

上的根本区别

具体来说,前缀和后缀 ++ 运算符 other 与运算符优先级之间是否有任何区别(可能是 javac 将命令转换为字节码的方式或在JVM 运行 是那种字节码)?

例如,下面的代码 必然 运行 相同(在每个 JVM 中):

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

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

JLS 中是否有任何内容定义这 2 个语句将 运行 在每个平台、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

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

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

不存在未定义或依赖于平台的行为,例如 C。

来自Oracle documentation

The increment expression is invoked after each iteration through the loop; it is perfectly acceptable for this expression to increment or decrement a value.

来自Oracle documentation on JLS

First, if the ForUpdate part is present, the expressions are evaluated in sequence from left to right; their values, if any, are discarded.

因为它是在 迭代和 return 值被丢弃之后调用的,所以这两个选项之间应该没有任何区别。可能字节码不完全一样,但应该是等价的。

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

它有四个部分:

  • 一个初始化部分
  • 条件部分
  • 循环变量修改部分
  • 要循环的 body 部分

任何编译器都需要单独处理它们,它们不能通过以任何方式加入它们来优化。 (无论如何,根据我的说法。) 每个部分都必须被视为某种孤立的实体。本次讨论我们只对循环变量修改部分感兴趣

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

这意味着答案必须是:是的,他们必须以同样的方式运行。

规范的相关部分是:

15.15.1. Prefix Increment Operator ++

[…]

At run time, if evaluation of the operand expression completes abruptly, then the prefix increment expression completes abruptly for the same reason and no incrementation occurs. Otherwise, the value 1 is added to the value of the variable and the sum is stored back into the variable. […]
The value of the prefix increment expression is the value of the variable after the new value is stored.

15.14.2. Postfix Increment Operator ++

[…]

At run time, if evaluation of the operand expression completes abruptly, then the postfix increment expression completes abruptly for the same reason and no incrementation occurs. Otherwise, the value 1 is added to the value of the variable and the sum is stored back into the variable. […]
The value of the postfix increment expression is the value of the variable before the new value is stored.

因此, 差异是在表达式上下文中使用时的结果值。在 for 循环的更新子句的上下文中使用它时,相关部分是:

14.14.1.2. Iteration of for Statement

[…]

First, if the ForUpdate part is present, the expressions are evaluated in sequence from left to right; their values, if any, are discarded. If evaluation of any expression completes abruptly for some reason, the for statement completes abruptly for the same reason; any ForUpdate statement expressions to the right of the one that completed abruptly are not evaluated.

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

所以答案是,代码可能有差异,例如天真地编译时,可观察到的行为保证是相同的。