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。
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.
字面意思 代码会有所不同,因为这些表达式会产生不同的结果,然后将其丢弃。然而,这种差异在于不可观察的行为,因此,代码通常被编译为首先不产生值,因此没有差异。
所以答案是,代码可能有差异,例如天真地编译时,可观察到的行为保证是相同的。
关于这个有几个问题(比如 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。
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.
字面意思 代码会有所不同,因为这些表达式会产生不同的结果,然后将其丢弃。然而,这种差异在于不可观察的行为,因此,代码通常被编译为首先不产生值,因此没有差异。
所以答案是,代码可能有差异,例如天真地编译时,可观察到的行为保证是相同的。