Java 中 i++ 与 ++i 的区别及常见误区解析
在 Java 编程中,我们经常会用到自增操作符:i++
(后置递增) 和 ++i
(前置递增)。虽然它们看起来很相似,但在执行顺序和返回值上存在显著差异。理解这些差异可以帮助我们避免一些常见的错误,尤其是在处理复杂表达式或性能敏感场景时。
一、问题引入:HashMap 统计字符出现次数的案例
举个简单的例子,我们希望统计一个长字符串中每个字符出现的次数,并使用 HashMap<Character, Integer>
来实现:
String p = "abcdabcd";
HashMap<Character, Integer> map = new HashMap<>();for (int i = 0; i < p.length(); i++) {int count = map.getOrDefault(p.charAt(i), 0);map.put(p.charAt(i), count++);
}
❗运行结果:
{'a': 0, 'b': 0, 'c': 0, 'd': 0}
❓为什么会这样?
问题出在这一行代码:
map.put(p.charAt(i), count++);
这里使用的是 后置递增操作符 count++
,它的行为是:
- 先将当前
count
的值作为表达式的返回值; - 然后再对
count
自增。
也就是说,map.put(...)
实际上传入的是原来的 count
值(例如 0),而 count
虽然增加了,但并没有被再次写回 map
。
✅ 正确写法应该是:
map.put(p.charAt(i), ++count); // 或者先 count += 1,再 put
或者更清晰的方式:
map.put(p.charAt(i), count + 1);
二、前置递增 ++i
与后置递增 i++
的核心区别
特性 | i++ (后置递增) | ++i (前置递增) |
---|---|---|
表达式返回值 | 返回原始值 | 返回自增后的新值 |
执行顺序 | 先使用原值,再自增 | 先自增,再使用新值 |
示例说明:
int i = 5;
int a = i++; // a = 5,i = 6
int i = 5;
int b = ++i; // i = 6,b = 6
三、运算优先级与表达式陷阱
示例一:
int i = 3;
System.out.println(i++ * 2); // 输出 6(因为 i 是 3)
int j = 3;
System.out.println(++j * 2); // 输出 8(因为 j 先变为 4)
示例二(不推荐):
int i = 0;
int x = i++ + ++i; // 不同编译器可能结果不同,而且难以阅读
⚠️ 这类复合表达式容易引发歧义,建议拆分成多个语句以提高可读性和可维护性。
四、性能差异分析
对于基本类型(如 int
):
在大多数现代 JVM 实现中,i++
和 ++i
在循环中的性能几乎一致,甚至生成的字节码完全相同:
for (int i = 0; i < 10; i++) { ... } // 可能等效于 ++i
对于对象类型(如迭代器或自定义类):
obj++
:需要创建临时对象来保存原始值,可能会调用拷贝构造函数,带来额外开销。++obj
:直接修改对象本身,效率更高。
✅ 推荐:在涉及对象或自定义类型的自增操作中,优先使用
++obj
。
这也是为什么很多 LeetCode 题解中倾向于使用 ++i
而不是 i++
。
五、注意事项与最佳实践
场景 | 建议 |
---|---|
简单变量自增 | i++ 和 ++i 性能无明显差异 |
复杂表达式中 | 避免混合使用 i++ 和 ++i ,容易造成逻辑混乱 |
自定义类型或迭代器 | 使用 ++i 更高效 |
可读性要求高时 | 明确写出逻辑,比如 i = i + 1 |
循环控制变量 | 使用 ++i 更符合“先自增再使用”的语义 |
六、总结一句话记忆口诀
"i在前是原值,i在后先计算"
即:
i++
:表达式中使用的是i
的原始值;++i
:表达式中使用的是i
的新值。
七、附表对比
操作符 | 表达式返回值 | 是否立即自增 | 适用场景 |
---|---|---|---|
i++ | 原始值 | 否(之后) | 需保留旧值参与运算 |
++i | 新值 | 是 | 需要立刻使用更新后的值进行操作 |