【问题标题】:Difference between prefix and postfix ++ operators in JavaJava中前缀和后缀++运算符的区别
【发布时间】:2015-07-29 15:06:17
【问题描述】:

对此有一些问题(例如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 中是否有任何内容定义这两条语句在每个平台、Java 编译器、JVM 等上以完全相同的方式运行,或者这两条语句是否有可能(甚至理论上)以不同的方式运行?

【问题讨论】:

  • 只添加之前和之后不同...

标签: java jvm operators bytecode


【解决方案1】:

规范的相关部分是:

15.15.1. Prefix Increment Operator ++

[…]

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

 

15.14.2. Postfix Increment Operator ++

[…]

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

所以,只有的区别是在表达式上下文中使用时的结果值。在 for 循环的更新子句的上下文中使用它时,相关部分是:

14.14.1.2. Iteration of for Statement

[…]

首先,如果存在 ForUpdate 部分,则表达式按从左到右的顺序进行计算;它们的值(如果有)将被丢弃。如果任何表达式的求值由于某种原因突然完成,for 语句也会因为同样的原因而突然完成;突然完成的语句右侧的任何 ForUpdate 语句表达式都不会被计算。

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

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

【讨论】:

    【解决方案2】:

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

    它有四个部分:

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

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

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

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

    【讨论】:

      【解决方案3】:

      来自Oracle documentation

      increment 表达式在循环的每次迭代后被调用;这个表达式增加一个值是完全可以接受的。

      来自Oracle documentation on JLS

      首先,如果存在 ForUpdate 部分,则表达式按从左到右的顺序进行计算;它们的值(如果有)将被丢弃。

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

      【讨论】:

      • 我会将“可能”更改为“可能”,因为我们还没有检查每个编译器。
      • 赞成。我认为这是迄今为止最相关的答案(尽管看起来大多数人似乎没有意识到这一点)但我会等着看是否有人提供更多详细说明。
      【解决方案4】:

      是的,它会运行相同的。它会编译成相同的字节码,因此无论它是什么 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 编译器生成的字节码。在这两种情况下都没有区别。
      • 这很好,但您刚刚展示了 2 个编译器提供相同的输出 - 我问它是否会必然运行相同。是否有另一个 Java 编译器会产生不同的输出?
      • 观察到的行为必须相同:请参阅@Kayaman 答案。 理论上的性能可能会有所不同,但我看不出有任何原因。当然,您可以创建自己的符合 JLS 的字节码编译器(或 fork 现有的编译器),它会为后增量添加几条无用的指令,以证明这是可能的。
      • 我很清楚这一点。我的问题本质上是理论上的。
      【解决方案5】:

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

      没有像 C 中那样的未定义或平台相关行为。

      【讨论】:

        猜你喜欢
        • 2022-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-21
        • 1970-01-01
        • 2011-03-12
        相关资源
        最近更新 更多