文章目录
- 【3.1Java基础】Java运算符常见错误排查:10个高频编译运行错误一网打尽
-
- 导入语
- [1 ~> 整数除法结果被截断](#1 ~> 整数除法结果被截断)
-
- [1.1 错误现象](#1.1 错误现象)
- [1.2 错误原因](#1.2 错误原因)
- [1.3 解决方案](#1.3 解决方案)
- [2 ~> 把赋值 `=` 写成了比较 `==`(反之亦然)](#2 ~> 把赋值
=写成了比较==(反之亦然)) -
- [2.1 错误现象](#2.1 错误现象)
- [2.2 错误原因](#2.2 错误原因)
- [2.3 解决方案](#2.3 解决方案)
- [3 ~> 自增自减的"前置后置"搞混](#3 ~> 自增自减的"前置后置"搞混)
-
- [3.1 错误现象](#3.1 错误现象)
- [3.2 错误原因](#3.2 错误原因)
- [3.3 解决方案](#3.3 解决方案)
- [4 ~> 经典陷阱:`i = i++`](#4 ~> 经典陷阱:
i = i++) -
- [4.1 错误现象](#4.1 错误现象)
- [4.2 错误原因](#4.2 错误原因)
- [4.3 解决方案](#4.3 解决方案)
- [5 ~> 字符串拼接误吃后面的数字](#5 ~> 字符串拼接误吃后面的数字)
-
- [5.1 错误现象](#5.1 错误现象)
- [5.2 错误原因](#5.2 错误原因)
- [5.3 解决方案](#5.3 解决方案)
- [6 ~> 用 `==` 比较浮点数](#6 ~> 用
==比较浮点数) -
- [6.1 错误现象](#6.1 错误现象)
- [6.2 错误原因](#6.2 错误原因)
- [6.3 解决方案](#6.3 解决方案)
- [7 ~> 三元运算符的返回值类型不一致](#7 ~> 三元运算符的返回值类型不一致)
-
- [7.1 错误现象](#7.1 错误现象)
- [7.2 错误原因](#7.2 错误原因)
- [7.3 解决方案](#7.3 解决方案)
- [8 ~> 复合赋值运算符的类型转换陷阱](#8 ~> 复合赋值运算符的类型转换陷阱)
-
- [8.1 错误现象](#8.1 错误现象)
- [8.2 错误原因](#8.2 错误原因)
- [8.3 解决方案](#8.3 解决方案)
- [9 ~> 逻辑运算符短路导致的副作用漏掉](#9 ~> 逻辑运算符短路导致的副作用漏掉)
-
- [9.1 错误现象](#9.1 错误现象)
- [9.2 错误原因](#9.2 错误原因)
- [9.3 解决方案](#9.3 解决方案)
- [10 ~> 运算符优先级导致结果出人意料](#10 ~> 运算符优先级导致结果出人意料)
-
- [10.1 错误现象](#10.1 错误现象)
- [10.2 错误原因](#10.2 错误原因)
- [10.3 解决方案](#10.3 解决方案)
- [思考 && 总结](#思考 && 总结)
- 结尾
【3.1Java基础】Java运算符常见错误排查:10个高频编译运行错误一网打尽
📖 文章简介: 本文是Java运算符的专项错误排查手册,整理了初学者在使用运算符时最高频的10个编译/运行错误。每个错误均采用"错误现象→错误原因→解决方案"三段式拆解,覆盖了整除截断、=与==混淆、自增自减陷阱、字符串拼接意外、浮点数相等判断、三元运算符类型不匹配、复合赋值隐藏的类型转换、除零异常、逻辑短路导致的副作用遗漏、以及括号缺失引发的优先级翻车。文末附三连击排查思路,适合刚学完运算符但总是编译报错的同学对照排查。

🎬 个人主页: 源码骑士
❄ 专栏传送门: 《java编程练习题》《全栈开发》
⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂
🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以流程图为特色,被读者评价为"看一篇胜过啃一周核心竞争力"
导入语
学完运算符之后,你兴冲冲地打开 Idea 准备写几个运算练手。结果刚敲了几行,要么编译器标红一片,要么程序跑起来输出跟你想的完全不一样。
这不是你的问题。运算符看似简单(加减乘除谁不会?),但 Java 的强类型加上一些语言特性,确实挖了不少坑。这篇文章就是专门来填这些坑的。每个错误都用"现象→原因→解决方案"拆开来讲,看完之后,你拿到一份报错信息就知道怎么排查。
1 ~> 整数除法结果被截断
1.1 错误现象
写了一段代码想算平均值,结果怎么算都是整数:
java
int math = 85;
int english = 92;
double avg = (math + english) / 2;
System.out.println("平均分:" + avg); // 输出:88.0(期望88.5)
1.2 错误原因
两个整数相除,结果还是整数。 小数部分直接截断 ,不会四舍五入。(85 + 92) / 2 = 177 / 2 = 88,整数 88 赋给 double 变量后才变成 88.0,但小数部分早就丢掉了。
1.3 解决方案
方案一: 让其中一个操作数变成浮点数:
java
double avg1 = (math + english) / 2.0; // 88.5
double avg2 = (double)(math + english) / 2; // 88.5
方案二: 分子分母都转:
java
double avg3 = (math + english) * 1.0 / 2; // 88.5
记住:除号两边只要有一个浮点数,就是小数除法。
2 ~> 把赋值 = 写成了比较 ==(反之亦然)
2.1 错误现象
现象A: 条件判断时不小心用了 =:
java
int score = 60;
if (score = 100) { // ❌ 编译报错
System.out.println("满分");
}
报错信息: incompatible types: int cannot be converted to boolean
现象B: 赋值的意图却写了 ==(通常不会报错,但逻辑完全错误):
java
boolean flag = (score == 100); // 意图是"score 等于100吗?"✅
flag == true; // 想重新赋值 flag = true,但这里只做了比较,结果丢弃了
System.out.println(flag); // 输出:false(flag 根本没变)
2.2 错误原因
= 是赋值运算符,把右边的值放进左边的变量。== 是比较运算符,判断两边是否相等,结果是 boolean 类型。两者键盘上紧挨着,手快就打错了。
2.3 解决方案
写条件判断时,把常量放在前面 ,这样即使误写成 = 编译器也会报错:
java
// ❌ 如果误写成 if (score = 100),编译通过但逻辑错误
if (score == 100) { }
// ✅ 把常量放前面:如果误写成 if (100 = score),编译器直接报错
if (100 == score) { }
这个写法叫"Yoda 条件",常量在前,变量在后,打错时编译器救你。
3 ~> 自增自减的"前置后置"搞混
3.1 错误现象
java
public class IncrementBug {
public static void main(String[] args) {
int x = 5;
int y = x++; // 以为 y = 6,实际上 y = 5
System.out.println("x = " + x + ", y = " + y);
// 输出:x = 6, y = 5
}
}
3.2 错误原因
这是初学者踩得最多的坑。x++(后置)的执行顺序是先用 x 当前的值参与运算,再把 x 加 1 。所以 y = x++ 等价于:
java
int temp = x; // temp = 5(保存 x 的当前值)
x = x + 1; // x = 6
y = temp; // y = 5
如果用的是 ++x(前置),结果才对:
java
int z = ++x; // x 先变成 6,再赋给 z,z = 6
3.3 解决方案
| 你的意图 | 正确写法 | 错误写法 |
|---|---|---|
| 先加 1,再赋值 | y = ++x; |
y = x++; |
| 先赋值,再加 1 | y = x++; |
y = ++x; |
| 不确定,求稳 | x++; y = x; |
别写一行 |
初学阶段最简单的避坑法: 把自增自减单独写一行,不要和赋值混在一起。
x++; y = x;两行清清楚楚。
4 ~> 经典陷阱:i = i++
4.1 错误现象
java
int i = 1;
i = i++;
System.out.println(i); // 输出:1(期望是2!)
4.2 错误原因
这是前一个问题的升级版。i = i++ 的执行步骤非常反直觉:
java
1. 先把 i 的当前值(1)压入操作数栈(Java 虚拟机的临时存储区)
2. i 自增为 2
3. 赋值:i = 操作数栈中保存的值(1)
4. 最终 i 又变回了 1
Java 中 ++ 的优先级高于 =,但后置自增的"先用后加"特性让赋值发生在自增之后,而赋的值又是自增前的旧值。这一套组合拳打下来,值就不对了。
4.3 解决方案
永远不要写 i = i++。如果你想让 i 自增:
java
i++; // ✅ 简洁明了
// 或
i = i + 1; // ✅ 语义清晰
5 ~> 字符串拼接误吃后面的数字
5.1 错误现象
java
int a = 10, b = 20;
System.out.println("结果:" + a + b); // 输出:结果:1020(期望:结果:30)
System.out.println(a + b + "是结果"); // 输出:30是结果(这个对了)
5.2 错误原因
+ 运算符遇到 String 时,行为会从加法 变成字符串拼接 。而且拼接是从左往右依次执行的:
java
"结果:" + a + b
→ "结果:" + 10 + 20 // 字符串 + int → 字符串拼接
→ "结果:10" + 20 // 字符串 + int → 仍然是拼接
→ "结果:1020"
反过来,先算 a + b 就不一样了:
java
a + b + "是结果"
→ 10 + 20 + "是结果" // int + int → 加法
→ 30 + "是结果" // int + String → 拼接
→ "30是结果"
5.3 解决方案
只要用括号把先算的部分括起来:
java
System.out.println("结果:" + (a + b)); // 输出:结果:30
System.out.println(a + " + " + b + " = " + (a + b)); // 输出:10 + 20 = 30
一句话:
+两边有一个是 String 就拼接,全是数字就加法。不确定就加括号。
6 ~> 用 == 比较浮点数
6.1 错误现象
java
double d1 = 0.1 + 0.2;
double d2 = 0.3;
System.out.println(d1 == d2); // 输出:false(期望 true)
6.2 错误原因
浮点数在计算机里是以二进制形式存储的,而十进制的 0.1 和 0.2 在二进制中是无限循环小数,只能存储近似值。所以 0.1 + 0.2 实际存储的是 0.30000000000000004,跟 0.3 有一丁点偏差,== 比较自然为 false。
6.3 解决方案
方案一: 比较差值是否小于一个很小的阈值:
java
double epsilon = 0.0000001;
boolean isEqual = Math.abs(d1 - d2) < epsilon;
System.out.println(isEqual); // 输出:true
方案二: 涉及金额的精确计算用 BigDecimal(后面学到再细讲):
java
import java.math.BigDecimal;
BigDecimal bd1 = new BigDecimal("0.1");
BigDecimal bd2 = new BigDecimal("0.2");
BigDecimal result = bd1.add(bd2); // 精确等于 0.3
初学阶段记住:"浮点数不要用
==比较",就够了。
7 ~> 三元运算符的返回值类型不一致
7.1 错误现象
java
int score = 80;
// 想用三元运算符赋不同提示语
String msg = (score >= 60) ? "及格" : 100; // ❌ 编译报错
报错信息: incompatible types: bad type in conditional expression
7.2 错误原因
三元运算符的 ? 后面的两个结果值必须是兼容的类型 。上面代码中一个是 String,一个是 int(100),编译器不知道整个表达式的类型是什么,直接报错。
7.3 解决方案
让两个结果值的类型保持一致:
java
String msg = (score >= 60) ? "及格" : "100"; // ✅ 两边都是 String
int grade = (score >= 60) ? 1 : 0; // ✅ 两边都是 int
如果确实需要不同类型的结果,换用 if-else:
java
String msg;
if (score >= 60) {
msg = "及格";
} else {
msg = "0";
}
8 ~> 复合赋值运算符的类型转换陷阱
8.1 错误现象
java
short s = 1;
s = s + 1; // ❌ 编译报错:incompatible types: possible lossy conversion from int to short
s += 1; // ✅ 编译通过
奇怪:s += 1 和 s = s + 1 不是等价的吗?
8.2 错误原因
它们语义上等价,但编译器处理方式不同:
s = s + 1:右边s + 1运算时,s先自动提升为 int,加完结果是 int,不能直接赋给 short,报错。s += 1:Java 语言规范规定,复合赋值运算符隐式包含强制类型转换 ,相当于s = (short)(s + 1)。
8.3 解决方案
两种写法都可以,但要理解它们的行为差异:
java
short s = 1;
// 方案一:显式强转(清晰,推荐)
s = (short)(s + 1);
// 方案二:直接用复合赋值(简洁,但隐藏了转换)
s += 1;
如果运算可能导致溢出(比如 short 加到超过 32767),用显式强转更安全,至少你知道自己在冒什么风险。
9 ~> 逻辑运算符短路导致的副作用漏掉
9.1 错误现象
想用短路特性链式调用,结果第二个操作数根本没执行:
java
int a = 5;
int b = 3;
boolean result = (a > 10) && (b++ > 0); // 期望 b 自增为 4
System.out.println("b = " + b); // 输出:b = 3(b++ 根本没执行!)
9.2 错误原因
&& 的短路特性:如果左边是 false,右边直接跳过。因为 a > 10 为 false,所以 b++ 根本没机会执行。同理,|| 左边为 true 时右边也跳过。
9.3 解决方案
不要把有"副作用"(改变变量值)的操作写在逻辑运算符里。 如果确实需要根据条件修改值,拆开来写:
java
if (a > 10) {
b++; // 条件成立才自增
}
boolean result = (a > 10) ? true : false;
一种利用短路特性的正确实战场景------判空保护:
java
String name = null;
// 短路保证 name 为 null 时,不会执行 equals()
if (name != null && name.equals("admin")) {
System.out.println("管理员登录");
}
原则: 短路特性只用来做预判保护(
!= null &&),不要在里面偷偷改变量值。
10 ~> 运算符优先级导致结果出人意料
10.1 错误现象
java
int result = 10 + 3 * 2; // 输出:16(先乘后加,内心期望 26)
boolean flag = true || false && false; // 输出:true(&& 优先级高于 ||)
int shift = 4 << 1 + 1; // 输出:16(+ 优先级高于 <<,等价于 4 << 2)
10.2 错误原因
Java 的运算符优先级很细,人脑很难全部记全。上面三个例子:
| 表达式 | 实际执行顺序 | 结果 | 可能误以为 |
|---|---|---|---|
10 + 3 * 2 |
10 + (3 * 2) |
16 | (10 + 3) * 2 = 26 |
| `true | false && false` | `true | |
4 << 1 + 1 |
4 << (1 + 1) |
16 | (4 << 1) + 1 = 9 |
10.3 解决方案
写一个铁律:拿不准优先级的时候,加括号。 括号里的先算,这个规则在全世界所有编程语言里都成立。加括号不丢人,也不影响性能:
java
int result1 = 10 + (3 * 2); // ✅ 明确:10 + 6 = 16
int result2 = (10 + 3) * 2; // ✅ 明确:13 * 2 = 26
boolean flag = true || (false && false); // ✅ 明确
int shift = 4 << (1 + 1); // ✅ 明确:4 << 2 = 16
思考 && 总结
本文拆解了 Java 运算符使用过程中最高频的 10 个编译/运行错误,核心排查思路如下:
- 整数除法截断: 两个整数相除结果还是整数,小数点后面直接砍掉。让除数或被除数变成浮点数(如
/ 2.0)即可得到小数结果。 =和==混淆:=是赋值,==是比较。条件判断用"Yoda 写法"(常量在前)可让编译器帮你抓出笔误。- 自增自减前后置: 前置
++x先加后用,后置x++先用后加。初学阶段把自增单独写一行,别跟赋值混一起。i = i++永远不要写。 - 字符串拼接吞数字:
"结果:" + a + b从左往右依次拼接,a+b变成字符串拼接而不是加法。加括号(a+b)解决。 - 浮点数别用
==: 二进制存储的精度问题导致0.1 + 0.2 != 0.3。用差值比较替代等于判断,精度敏感场景用BigDecimal。 - 三元运算符类型一致性:
?后面的两个返回值类型必须兼容,一个 String 一个 int 编译直接报错。 - 复合赋值的隐式转换:
s += 1包含自动强制转换,s = s + 1不包含。知道这个差异就行,日常用显式强转更安全。 - 短路导致副作用遗漏:
&&左边 false 右边不执行。利用短路做!= null保护是好事,但不要把改变量值的操作藏在右边。 - 优先级猜错: 不确定就加括号。括号不花钱也不减速,但能让你和后来看代码的人都省去猜优先级的功夫。
终极排查思路:
bash
拿到一个运算结果不对的问题
├─ 第一步:看有没有类型提升(整数除法?int + double?)
├─ 第二步:看有没有字符串参与(+ 拼接了?顺序对吗?)
├─ 第三步:看有没有自增自减(前置还是后置?跟赋值写在一起了吗?)
├─ 第四步:看有没有优先级坑(加个括号再跑一次试试?)
└─ 第五步:看有没有短路逻辑(&& 或 || 右边的代码实际执行了吗?)
结尾
各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!
源码骑士 --- Android Framework & 全栈开发
👀 关注:跟博主一起从源码视角深耕底层原理,见证每一次成长
❤️ 点赞:让优质内容被更多人看见,让知识传递更有力量
⭐ 收藏:把核心知识点存好,在需要时随时查、随时用
💬 评论:分享你的经验或疑问,评论区一起交流避坑
🔄 一键四连:不要忘记给博主"一键四连"哦!今日源码拆解达成!
🗡️ 寄语:技术之路难免有困惑,但同行的人会让前进更有方向
结语:编译报错不是你的敌人,是编译器在帮你找 bug。希望这篇文章能帮你拿下运算符阶段的所有编译运行错误。不要忘记给博主"一键四连"哦!
往期回顾:
【1.Java基础】Java初识:从零搭建开发环境到写出第一个HelloWorld
【1.1Java基础】JDK安装常见问题教辅-从踩坑到排雷
【1.2Java基础】Win10环境变量配置详解-从原理到排雷
【2.Java基础】Java常量与变量-从基本类型到类型转换全面掌握
【3.Java基础】Java运算符详解:从算数运算到逻辑判断