【3.1Java基础】Java运算符常见错误排查:10个高频编译运行错误一网打尽
文章目录
- 【3.1Java基础】Java运算符常见错误排查:10个高频编译运行错误一网打尽
- 导入语
- 1 ~> 整数除法结果被截断
- 1.1 错误现象
- 1.2 错误原因
- 1.3 解决方案
- 2 ~> 把赋值 `=` 写成了比较 `==`(反之亦然)
- 2.1 错误现象
- 2.2 错误原因
- 2.3 解决方案
- 3 ~> 自增自减的"前置后置"搞混
- 3.1 错误现象
- 3.2 错误原因
- 3.3 解决方案
- 4 ~> 经典陷阱:`i = i++`
- 4.1 错误现象
- 4.2 错误原因
- 4.3 解决方案
- 5 ~> 字符串拼接误吃后面的数字
- 5.1 错误现象
- 5.2 错误原因
- 5.3 解决方案
- 6 ~> 用 `==` 比较浮点数
- 6.1 错误现象
- 6.2 错误原因
- 6.3 解决方案
- 7 ~> 三元运算符的返回值类型不一致
- 7.1 错误现象
- 7.2 错误原因
- 7.3 解决方案
- 8 ~> 复合赋值运算符的类型转换陷阱
- 8.1 错误现象
- 8.2 错误原因
- 8.3 解决方案
- 9 ~> 逻辑运算符短路导致的副作用漏掉
- 9.1 错误现象
- 9.2 错误原因
- 9.3 解决方案
- 10 ~> 运算符优先级导致结果出人意料
- 10.1 错误现象
- 10.2 错误原因
- 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 错误现象
写了一段代码想算平均值,结果怎么算都是整数:
intmath=85;intenglish=92;doubleavg=(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 解决方案
方案一:让其中一个操作数变成浮点数:
doubleavg1=(math+english)/2.0;// 88.5doubleavg2=(double)(math+english)/2;// 88.5方案二:分子分母都转:
doubleavg3=(math+english)*1.0/2;// 88.5记住:除号两边只要有一个浮点数,就是小数除法。
2 ~> 把赋值=写成了比较==(反之亦然)
2.1 错误现象
现象A:条件判断时不小心用了=:
intscore=60;if(score=100){// ❌ 编译报错System.out.println("满分");}报错信息:incompatible types: int cannot be converted to boolean
现象B:赋值的意图却写了==(通常不会报错,但逻辑完全错误):
booleanflag=(score==100);// 意图是"score 等于100吗?"✅flag==true;// 想重新赋值 flag = true,但这里只做了比较,结果丢弃了System.out.println(flag);// 输出:false(flag 根本没变)2.2 错误原因
=是赋值运算符,把右边的值放进左边的变量。==是比较运算符,判断两边是否相等,结果是 boolean 类型。两者键盘上紧挨着,手快就打错了。
2.3 解决方案
写条件判断时,把常量放在前面,这样即使误写成=编译器也会报错:
// ❌ 如果误写成 if (score = 100),编译通过但逻辑错误if(score==100){}// ✅ 把常量放前面:如果误写成 if (100 = score),编译器直接报错if(100==score){}这个写法叫"Yoda 条件",常量在前,变量在后,打错时编译器救你。
3 ~> 自增自减的"前置后置"搞混
3.1 错误现象
publicclassIncrementBug{publicstaticvoidmain(String[]args){intx=5;inty=x++;// 以为 y = 6,实际上 y = 5System.out.println("x = "+x+", y = "+y);// 输出:x = 6, y = 5}}3.2 错误原因
这是初学者踩得最多的坑。x++(后置)的执行顺序是先用 x 当前的值参与运算,再把 x 加 1。所以y = x++等价于:
inttemp=x;// temp = 5(保存 x 的当前值)x=x+1;// x = 6y=temp;// y = 5如果用的是++x(前置),结果才对:
intz=++x;// x 先变成 6,再赋给 z,z = 63.3 解决方案
| 你的意图 | 正确写法 | 错误写法 |
|---|---|---|
| 先加 1,再赋值 | y = ++x; | y = x++; |
| 先赋值,再加 1 | y = x++; | y = ++x; |
| 不确定,求稳 | x++;y = x; | 别写一行 |
初学阶段最简单的避坑法:把自增自减单独写一行,不要和赋值混在一起。
x++; y = x;两行清清楚楚。
4 ~> 经典陷阱:i = i++
4.1 错误现象
inti=1;i=i++;System.out.println(i);// 输出:1(期望是2!)4.2 错误原因
这是前一个问题的升级版。i = i++的执行步骤非常反直觉:
1.先把 i 的当前值(1)压入操作数栈(Java虚拟机的临时存储区)2.i 自增为23.赋值:i=操作数栈中保存的值(1)4.最终 i 又变回了1Java 中++的优先级高于=,但后置自增的"先用后加"特性让赋值发生在自增之后,而赋的值又是自增前的旧值。这一套组合拳打下来,值就不对了。
4.3 解决方案
永远不要写i = i++。如果你想让i自增:
i++;// ✅ 简洁明了// 或i=i+1;// ✅ 语义清晰5 ~> 字符串拼接误吃后面的数字
5.1 错误现象
inta=10,b=20;System.out.println("结果:"+a+b);// 输出:结果:1020(期望:结果:30)System.out.println(a+b+"是结果");// 输出:30是结果(这个对了)5.2 错误原因
+运算符遇到 String 时,行为会从加法变成字符串拼接。而且拼接是从左往右依次执行的:
"结果:"+a+b →"结果:"+10+20// 字符串 + int → 字符串拼接→"结果:10"+20// 字符串 + int → 仍然是拼接→"结果:1020"反过来,先算a + b就不一样了:
a+b+"是结果"→10+20+"是结果"// int + int → 加法→30+"是结果"// int + String → 拼接→"30是结果"5.3 解决方案
只要用括号把先算的部分括起来:
System.out.println("结果:"+(a+b));// 输出:结果:30System.out.println(a+" + "+b+" = "+(a+b));// 输出:10 + 20 = 30一句话:
+两边有一个是 String 就拼接,全是数字就加法。不确定就加括号。
6 ~> 用==比较浮点数
6.1 错误现象
doubled1=0.1+0.2;doubled2=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 解决方案
方案一:比较差值是否小于一个很小的阈值:
doubleepsilon=0.0000001;booleanisEqual=Math.abs(d1-d2)<epsilon;System.out.println(isEqual);// 输出:true方案二:涉及金额的精确计算用BigDecimal(后面学到再细讲):
importjava.math.BigDecimal;BigDecimalbd1=newBigDecimal("0.1");BigDecimalbd2=newBigDecimal("0.2");BigDecimalresult=bd1.add(bd2);// 精确等于 0.3初学阶段记住:“浮点数不要用
==比较”,就够了。
7 ~> 三元运算符的返回值类型不一致
7.1 错误现象
intscore=80;// 想用三元运算符赋不同提示语Stringmsg=(score>=60)?"及格":100;// ❌ 编译报错报错信息:incompatible types: bad type in conditional expression
7.2 错误原因
三元运算符的?后面的两个结果值必须是兼容的类型。上面代码中一个是String,一个是int(100),编译器不知道整个表达式的类型是什么,直接报错。
7.3 解决方案
让两个结果值的类型保持一致:
Stringmsg=(score>=60)?"及格":"100";// ✅ 两边都是 Stringintgrade=(score>=60)?1:0;// ✅ 两边都是 int如果确实需要不同类型的结果,换用if-else:
Stringmsg;if(score>=60){msg="及格";}else{msg="0";}8 ~> 复合赋值运算符的类型转换陷阱
8.1 错误现象
shorts=1;s=s+1;// ❌ 编译报错:incompatible types: possible lossy conversion from int to shorts+=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 解决方案
两种写法都可以,但要理解它们的行为差异:
shorts=1;// 方案一:显式强转(清晰,推荐)s=(short)(s+1);// 方案二:直接用复合赋值(简洁,但隐藏了转换)s+=1;如果运算可能导致溢出(比如 short 加到超过 32767),用显式强转更安全,至少你知道自己在冒什么风险。
9 ~> 逻辑运算符短路导致的副作用漏掉
9.1 错误现象
想用短路特性链式调用,结果第二个操作数根本没执行:
inta=5;intb=3;booleanresult=(a>10)&&(b++>0);// 期望 b 自增为 4System.out.println("b = "+b);// 输出:b = 3(b++ 根本没执行!)9.2 错误原因
&&的短路特性:如果左边是false,右边直接跳过。因为a > 10为false,所以b++根本没机会执行。同理,||左边为true时右边也跳过。
9.3 解决方案
不要把有"副作用"(改变变量值)的操作写在逻辑运算符里。如果确实需要根据条件修改值,拆开来写:
if(a>10){b++;// 条件成立才自增}booleanresult=(a>10)?true:false;一种利用短路特性的正确实战场景——判空保护:
Stringname=null;// 短路保证 name 为 null 时,不会执行 equals()if(name!=null&&name.equals("admin")){System.out.println("管理员登录");}原则:短路特性只用来做预判保护(
!= null &&),不要在里面偷偷改变量值。
10 ~> 运算符优先级导致结果出人意料
10.1 错误现象
intresult=10+3*2;// 输出:16(先乘后加,内心期望 26)booleanflag=true||false&&false;// 输出:true(&& 优先级高于 ||)intshift=4<<1+1;// 输出:16(+ 优先级高于 <<,等价于 4 << 2)10.2 错误原因
Java 的运算符优先级很细,人脑很难全部记全。上面三个例子:
| 表达式 | 实际执行顺序 | 结果 | 可能误以为 |
|---|---|---|---|
10 + 3 * 2 | 10 + (3 * 2) | 16 | (10 + 3) * 2 = 26 |
true || false && false | true || (false && false) | true | (true || false) && false = false |
4 << 1 + 1 | 4 << (1 + 1) | 16 | (4 << 1) + 1 = 9 |
10.3 解决方案
写一个铁律:拿不准优先级的时候,加括号。括号里的先算,这个规则在全世界所有编程语言里都成立。加括号不丢人,也不影响性能:
intresult1=10+(3*2);// ✅ 明确:10 + 6 = 16intresult2=(10+3)*2;// ✅ 明确:13 * 2 = 26booleanflag=true||(false&&false);// ✅ 明确intshift=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保护是好事,但不要把改变量值的操作藏在右边。 - 优先级猜错:不确定就加括号。括号不花钱也不减速,但能让你和后来看代码的人都省去猜优先级的功夫。
终极排查思路:
拿到一个运算结果不对的问题 ├─ 第一步:看有没有类型提升(整数除法?int + double?) ├─ 第二步:看有没有字符串参与(+ 拼接了?顺序对吗?) ├─ 第三步:看有没有自增自减(前置还是后置?跟赋值写在一起了吗?) ├─ 第四步:看有没有优先级坑(加个括号再跑一次试试?) └─ 第五步:看有没有短路逻辑(&&或||右边的代码实际执行了吗?)结尾
各位小伙伴,本文的内容到这里就全部结束了,源码骑士在这里再次感谢您的阅读!
源码骑士 — Android Framework & 全栈开发
👀关注:跟博主一起从源码视角深耕底层原理,见证每一次成长
❤️点赞:让优质内容被更多人看见,让知识传递更有力量
⭐收藏:把核心知识点存好,在需要时随时查、随时用
💬评论:分享你的经验或疑问,评论区一起交流避坑
🔄一键四连:不要忘记给博主"一键四连"哦!今日源码拆解达成!
🗡️寄语:技术之路难免有困惑,但同行的人会让前进更有方向
结语:编译报错不是你的敌人,是编译器在帮你找 bug。希望这篇文章能帮你拿下运算符阶段的所有编译运行错误。不要忘记给博主"一键四连"哦!
往期回顾:
【1.Java基础】Java初识:从零搭建开发环境到写出第一个HelloWorld
【1.1Java基础】JDK安装常见问题教辅-从踩坑到排雷
【1.2Java基础】Win10环境变量配置详解-从原理到排雷
【2.Java基础】Java常量与变量-从基本类型到类型转换全面掌握
【3.Java基础】Java运算符详解:从算数运算到逻辑判断
