资讯详情

Java运算符与表达式全解:优先级、短路、位运算与易错点

📅 2026/10/10 21:34:51 | 华诺云谱 👁 阅读
Java运算符与表达式全解:优先级、短路、位运算与易错点
1. 先搞懂运算符与表达式在Java中的位置这块基础为什么没人敢跳过很多新手学Java时有个错觉运算符不就加减乘除嘛没啥好学的。真到了写代码的时候才发现一个表达式能把人绕晕的永远不是加减乘除而是那堆看起来不起眼的双等号、三元问号、位运算符号以及编译器默默做的类型提升。你去看Java面试题随便翻一份基础卷几乎有一半的题都在拿运算符和表达式做文章比如i和i的区别、和的差别、a b c到底怎么赋值甚至有些公司笔试里直接让手算表达式求值。为什么这些基础题这么高频因为运算符和表达式是描述“程序行为”的最小语言单元。你写业务逻辑本质就是在写一堆赋值、比较、判断、拼接而这些全部要靠运算符串起来。你可以不记得某个集合类的API但不能不理解int x a b ? a : b这句话在JVM层面到底发生了什么。这个东西如果理解不透后面学分支、循环、方法重载、泛型、Lambda表达式都会卡壳因为那些语法里到处都藏着表达式的影子。这篇内容适合谁看两类人。一类是刚开始学Java、对运算符停留在“会算”层面的初学者我帮你把底层原理和易错点一次讲透另一类是准备校招或者跳槽面试的开发者我把面试里经常出现的运算符与表达式题目、坑点、排查思路整理成了速查结构你按着过一遍基本不会在基础题上丢分。我自己带过不少新人也看过不少线上代码发现很多真实故障并不是复杂架构导致的而是一个表达式写飘了比如位运算优先级算错导致条件判断失效、i在同一个表达式里被用了两次导致结果跟预期完全对不上。所以这篇不是干巴巴地列运算符表而是把“为什么这么设计”“实际用的时候容易踩什么坑”“线上排查怎么看这类问题”一起讲清楚。2. 运算符分类逐个拆从算术到位运算的一张全景表2.1 先建立一个整体框架Java里到底有哪些运算符Java里的运算符按功能来分大致有这几类算术运算符、关系运算符、逻辑运算符、赋值运算符、三元运算符、位运算符还有一个经常被忽略的字符串连接运算符。很多人数不全是因为平时只用了其中一半另一半只在特定场景出现比如做状态位取值、做权限掩码、做hash散列的时候才碰到位运算。我建议你把运算符当成一个“工具箱”来看每个工具解决一类问题。算术运算符解决数值计算关系运算符解决大小比较逻辑运算符解决多个条件组合赋值运算符解决变量存储三元运算符解决简单的二选一位运算符解决二进制层面的操作。用这个框架去记忆比死背符号表靠谱得多。下面这张表是全景总览建议新手先看个眼熟后面每一类我都会拆开细讲分类符号作用简述典型用法算术 - * / %数学计算a b、a % b自增自减 --变量加一或减一i、--i关系 !比较大小或相等a b、x y逻辑 || !布尔逻辑组合a b、!flag短路与 | ^布尔或整数位运算flag mask赋值 - * / %变量赋值a 5三元? :条件二选一a b ? a : b位运算 ~ | ^二进制偏移与位操作value 2字符串拼接连接字符串hello name有一点要特别提醒关系运算符里最容易写错的是。在实际开发中基本类型直接用比较没问题但两个对象用比较的是引用地址不是内容。很多初学者用str1 str2去判断两个字符串是否相等结果一会儿对一会儿错原因就是字符串常量池和对象引用在作祟。判断字符串内容相等老老实实用equals方法这是面试高频考点也是线上bug重灾区。2.2 算术运算符和取余负数和浮点数的两个隐藏细节算术运算符大家都会用但有两个细节值得单独拿出来讲。第一个是取余运算%的符号问题。Java的取余运算结果的符号和被除数左边的数保持一致而不是跟除数一致。比如-7 % 3的结果是-17 % -3的结果是1。为什么要强调这个因为有些业务里需要判断两个数是否整除或者算某个数在数组里的循环下标如果没考虑负数情况很容易出现越界或者结果错误。比如int index -7 % 3; // 结果是 -1而不是 2这在做循环队列、哈希散列的时候特别容易踩坑。如果希望无论如何都得到非负余数通常需要自己做修正例如((n % m) m) % m这种写法就是为了避免负余数影响逻辑。第二个是浮点数的运算精度问题。0.1 0.2在Java里并不等于0.3而是0.30000000000000004。这个坑无数人踩过原因是浮点数在计算机里是按二进制科学计数法存储的十进制小数转二进制很多时候是无限循环小数计算机只能截断存储于是产生了误差。如果你在写金额计算、百分比计算一定要记住浮点数不精确不要直接用比较浮点结果也不要依赖浮点运算做精确的金额累计。面试时如果被问到“浮点数如何比较”标准回答是先设定一个误差范围比如Math.abs(a - b) 1e-9或者使用BigDecimal。2.3 自增自减i和i不是只差一个位置i和i是Java里辨识度极高又极其容易出错的点。先说结论i是先使用i的当前值再把i加1i是先把i加1再使用新值。单独写一行的时候两者没区别但放在表达式里就完全不同了。int a 5; int b a; // b 5a 6 int c a; // a 7c 7这段代码里b取到的是a自增之前的5然后a才变成6到了c这里a先变成7再把7赋给c。这个顺序如果理解错了写循环、写数组下标、写计数器的时候都会出现莫名其妙的“差一”。线上最常见的一个坑是list.get(i)和list.get(i)混用。前者是先取出当前元素再移动下标后者是先移动下标再取元素只要逻辑一混淆数据顺序就乱了。另外多个自增在同一个表达式里出现时代码会变得几乎不可读比如int result i i i我劝大家不要在生产代码里写这种表达式因为你很难一眼看出结果而且不同JDK版本对这类表达式的求值顺序虽然有一致规范但读代码的人是很容易看晕的。工作中保持表达式的简单和明确比炫技重要得多。2.4 逻辑运算符短路的含义不是所有课程都讲透了逻辑运算符、||和!是写条件判断的主力。它们有对应的位运算版和|看起来只差一个字符行为却差很多。关键区别就在“短路”上。的短路含义是如果左边已经能确定整个表达式为false那么右边的表达式根本不会执行。||同理左边是true整个表达式已经为true右边也不会执行。举个例子String s null; if (s ! null s.length() 0) { // 安全访问s为空时不会执行 s.length() }这里s ! null先执行如果s是null结果为false整个条件短路后面的s.length()根本不会调用也就不会抛空指针。这是Java写判空条件的经典写法。如果你把换成逻辑含义虽然相似但不会短路s.length()照样会执行空指针直接炸。所以实际开发中判断条件几乎只用和||位运算版和|则用在布尔位掩码的场合。面试题里有个经典陷阱if (a || (b 5))和if (a | (b 5))的区别。前者如果a为trueb不会执行后者不管a是什么b都会执行。这种题不是在考你会不会计算而是在考你对“求值时机”的理解。我见过网上有段子调侃程序员半夜被叫起来修复生产事故发现是写成了虽说是段子但现实中这类错误导致的空指针罚单确实不少。2.5 三元运算符和赋值运算符代码简洁的代价是什么三元运算符? :本质是“单行if-else”。a b ? a : b的意思就是如果a b成立就返回a否则返回b。它最大的价值是让简单选择变得简练比如求最大值int max x y ? x : y;而不是写三行if-else。但三元运算符有个典型坑点冒号两边如果一个是基本类型一个是包装类型或者一个是int一个是double结果会按照二进制数值提升规则自动转换很容易出现“你以为返回的是包装类结果拿到了另一个类型”的情况。我举个例子Object result condition ? 1 : 2.0因为一个是Integer一个是Double最终结果会被提升成Double。这种情况下如果后续代码按整数来强转就会直接报类型转换异常。所以使用三元运算符尤其是两边类型不一致时一定要留意结果类型。赋值运算符的坑要少一些但有几个点值得说。-这类复合赋值运算符其实隐含着一次强转。比如short s 1; s 1;是合法的因为自带了强制转换而s s 1;却会编译报错因为s 1结果是int不能自动赋给short。这个细节面试里也经常考。2.6 位运算权限处理、标志位组合里的瑞士军刀位运算符包括、、、、|、^、~。日常业务代码里用得少但一旦涉及高性能计算、网络协议解析、状态位合并位运算是不可替代的。先理解几个基本概念。 n相当于乘以2的n次方 n相当于除以2的n次方但要注意右移分两种是有符号右移最高位补符号位是无符号右移最高位补0。这个区别在处理负数时特别明显int a -8; int b a 1; // 结果是 -4符号位保留 int c a 1; // 结果是很大的正数因为最高位补0面试题里让你手写判断一个整数是不是2的幂次方标准解法和位运算强相关n 0 (n (n - 1)) 0。原理很简单2的幂次方的二进制表示只有一个1减一之后和原数做按位与会把唯一的1消掉结果一定是0。这种解法比循环除以2快得多而且很直观。我再举个例子用位运算做权限掩码。假设一个系统有读、写、执行三种权限可以用三个二进制位表示读1、写2、执行4。组合权限就是按位或比如同时给读和写权限是int perm 1 | 2;也就是3判断是否有写权限就是(perm 2) ! 0。这种写法的好处是把多个布尔状态塞进一个int里在数据库里存一个字段就能搞定比建三张关联表高效得多。我第一次看到项目里这么处理权限时也觉得复杂真正跑起来才发现位运算在大批量权限判断场景下性能优势很明显。注意位运算的优先级比关系运算符低比逻辑运算符高具体优先级我放到下一章统一讲。如果你拿不准写位运算表达式时多用括号包起来。代码可读性永远排在第一位。3. 优先级与结合性表达式求值顺序里最容易翻车的3个坑3.1 优先级速记法一张表记住不用背Java运算符优先级一直是让人头疼的部分。其实不需要背全表只需要记住几个关键层级就能覆盖绝大多数场景。我把从高到低的顺序简化为下面这张速记表优先级类别符号说明高后缀expr、expr--自增自减后缀一元expr、--expr、!、~、expr、-expr强制转换等乘法* / %算术乘除加法 -算术加减、字符串拼接移位 位偏移关系 instanceof大小比较相等 !相等比较按位与位与按位异或^位异或按位或|位或逻辑与短路与逻辑或||短路或三元? :条件低赋值 - * / % ...赋值记住几个关键优先级关系就行自增和一元运算最高然后是乘除加减然后是关系比较然后是相等判断然后是位运算最后是逻辑运算、三元和赋值。日常写代码碰到不确定的优先级直接加括号是最稳妥的别赌记忆也别总希望别人“能看懂你的简洁”。3.2 优先级翻车案例一和到底谁先算我见过一次真实的事故排查像这样的一行代码if (flags MASK 1) { // 期望判断 flags 与 MASK 按位与后是否等于1 }这行代码在Java里会怎么执行因为的优先级高于所以实际编译出来是flags (MASK 1)也就是先比较MASK 1得到一个布尔值再用这个布尔值和flags做按位与。布尔值参与位运算时会被转成1或0最终的判断结果和你预期的完全不同。这段代码能编译通过但逻辑完全错了而且错得极隐蔽。正确的写法是if ((flags MASK) 1) { // 先做按位与再比较结果 }这个例子充分说明位运算的优先级比很多人直觉中的更低尤其是和混用时括号不是可选项是必须项。我在排查这种问题时的经验是先看表达式里有没有混合不同类别的运算符如果有就先把优先级表翻出来对照一遍不要凭“我印象里是先算这个”去猜。3.3 优先级翻车案例二三目运算符和赋值的纠缠三目运算符的优先级比赋值运算符高但比逻辑或低。同一个表达式里如果既有?:又有很容易让人觉得“这不可能编译通过吧”但它就是能编译。比如int a condition ? b : c 10;这种写法是把condition ? b : c的结果赋给a吗不是。实际解析可能是混乱的。为了不出这种怪代码我的建议很简单凡是有三元运算符的表达式全部用括号把真值分支和假值分支包起来比如int result (condition) ? b : c;确实多两个字符但换来的是不会有人纠结这行代码到底怎么算。3.4 结合性与表达式求值顺序左侧先行还是右侧先行优先级决定的是先算谁而结合性决定的是同优先级下从左算还是从右算。Java大部分运算符是左结合也就是从左往右算。赋值运算符是右结合所以a b c会把c赋给b再把b的值赋给a。但有两点要分清“结合性”和“求值顺序”不是一回事。Java保证的是一个表达式中的子表达式计算结果通常是先算左边再算右边除了赋值和三元等少数情况。比如int x a() b();Java规范保证先调用a()再调用b()即使a()和b()的顺序互换不影响最终加法结果但这决定了“副作用”的发生顺序。网上有一段经典例子int i 0; int result i i;这里先取左边的i为0参与加法i变成1再取右边的i为1参与加法i变成2最终结果是1。如果反过来先算右边结果就是2。这种差异面试官特别喜欢拿来考。我的建议是遇到对执行顺序敏感的自增写法就把它拆成多行写别让阅读代码的人去猜。4. 表达式求值过程类型提升、赋值细节和那些反向踩坑的写法4.1 二元运算之前类型悄悄发生的自动提升Java表达式求值时会先做类型提升。简单来说两个不同类型的值做运算小的类型会自动转成大的类型比如int long会先把int转成long再相加结果类型是long。float double会把float转成double结果是double。但short和short相加却会被提升成int结果也是int这是Java规范里容易被忽略的一条规则任何凡是可以转成int的操作数都会先转成int再做整型运算。这个规则带来的后果很直接short a 1; short b 2; short c a b; // 编译报错a b 结果是 int short d (short) (a b); // 必须强转所以很多初学者在写short、byte、char类型相加时报错时第一反应是“不是很正常的加法吗”实际上这是类型提升在起作用。理解这个机制对排查编译错误很有帮助。面试题里经常出现的“byte byte结果是什么类型”答案就是int。4.2 字符串连接符唯一一个能“重载”的运算符Java不支持运算符重载但是个例外。它在遇到字符串时会变成连接操作。这个“特殊性”带来一个老生常谈又必备的知识点字符串拼接时左右两边只要有一边是字符串就会把另一边转成字符串。常见面试题这样出System.out.println(1 2 3); // 输出 33 System.out.println(1 2 3); // 输出 123第一行先算1 2得到3再拼接字符3变成33。第二行先遇到字符串1然后 2变成12再 3变成123。结果差异的根源就是是左结合的而且“遇到字符串就转成字符串拼接”这件事是从左到右发生的。实际开发中字符串拼接大量存在尤其是在日志输出、SQL拼接、动态提示信息场景。性能上要注意如果在一个循环里反复用拼字符串会产生大量临时对象推荐用StringBuilder或StringBuffer来代替。不过单次的、几行的拼接直接用完全没问题可读性还好不必为了所谓的“性能”把所有都改成StringBuilder那属于过度优化。4.3 复合赋值运算符自带强转也可能埋坑复合赋值运算符包括、-、*、/、%等。刚才提过short s 1; s 1;能编译而s s 1;不能编译原因是复合赋值运算符会隐式强转。这个隐式强转在某些场景下反而会带来意想不到的结果。比如int a 200; a 100; // 结果 300没问题但如果类型比较窄byte b 100; b 50; // b -106发生了溢出的强转因为b 50先按int计算得到150再强转成bytebyte取值范围是-128到127150直接溢出变成-106。这种代码不报错但结果完全不符合预期。在处理字节流、解析协议头时很容易踩这种数值溢出坑。修复方式很简单遇到可能越界的运算用更宽的类型存储并做范围校验。4.4 三元运算符的类型规则不匹配时编译器按什么规则转之前我提到三元运算符的两边类型不一致时会按类型提升规则自动转换。这里深入讲一下。对于condition ? expr1 : expr2如果expr1和expr2类型不同编译器会尽量将两者提升到“可以赋给目标类型”的公共类型。一个高频面试题是这样的Object o1 true ? 1 : 2.0; System.out.println(o1);输出是1.0因为Integer和Double的公共类型是Double1被提升成1.0。但如果写Object o2 true ? 1 : text;这时一个是Integer一个是String它们没有数值提升关系编译器会把结果类型处理成Number或Serializable等公共父接口实际运行时o2才是指向Integer的对象。这题考察的就是混合类型三元的类型推导理解了提升规则基本不会再错。这个知识在写通用代码时特别有用比如写一个工具方法根据条件返回不同数值类型如果不注意统一类型很可能返回数据被隐式改变了。我见过一个报表导出功能原来返回值类型是Object某个分支用true ? 1 : 1.0f写结果所有整数都带了.0前端展示直接多了小数位。5. 动手练用4个小题目验证理解程度代码直接抄5.1 题一写一个判断闰年的表达式用多运算符组合不做任何算法优化就要求你用最普通的表达式把判断写出来。闰年的规则是能被4整除且不能被100整除或者能被400整除。用运算符组合翻译就是boolean isLeapYear (year % 4 0 year % 100 ! 0) || (year % 400 0);这道题是练习优先级和逻辑运算符搭配的好例子。注意观察我写的括号把的两个子条件放在同一个括号里再把||两边用括号分开。实际上不加括号也能正确执行因为优先级高于||但加了括号的意图更清晰。我在代码审查里经常看到类似逻辑只要不违反语义我建议把判断部分的括号写全不要让同事去心算优先级。5.2 题二两个变量的交换不用临时变量考察位运算和赋值运算符的实际使用。通过异或运算可以直接交换两个整数int a 3; int b 5; a a ^ b; b a ^ b; a a ^ b;异或交换的原理是任何一个数和另一个数异或两次会还原。第一步a ^ b存入a第二步a ^ b实际上变成了原来的a第三步再异或得到原来的b。这个写法看起来很酷但在真实项目中不推荐主要原因是可读性差而且对于现代CPU来说用临时变量交换并不慢反而更安全。面试时如果被问你能讲出原理就行实际干活还是用int tmp a; a b; b tmp;。5.3 题三用位运算判断一个整数是不是2的幂次方前面已经给出表达式n 0 (n (n - 1)) 0。这里的也很有讲究它保证了当n小于等于0时不需要执行后面的位运算。如果换成当n是0或者负数时后面的位运算虽然不会抛异常但结果可能变成1或其它值判断逻辑就完全失效了。我自己试过分别用和跑一组数据n 0时n 0 ...直接得到false而n 0 ...会把0 -1算出来是0进而0 0为true于是0被判成2的幂这显然是错的。所以这道题表面是在考位运算深一层是在考短路逻辑。5.4 题四字符串连接符与算术运算符的优先级实战把下面的表达式手算出来再写进代码验证int num 10; String msg result is (num 5) num;输出是result is 1510。过程是这样第一个遇到字符串result is 所以num 5因为有括号提前算出15拼接成result is 15紧接着再和num也就是10拼接得到result is 1510。注意如果我把括号去掉写成result is num 5 num因为从左到右结合会变成result is 10得到result is 10再5得到result is 105再num得到result is 10510结果完全不同。这就是求值顺序和运算符优先级的综合体现。6. 面试高频题与排查速查表背会这些心里就有底6.1 高频面试题汇总这里整理了我收集到的以及我在面试别人时喜欢问的运算符与表达式相关题目全是基础高频但答全对的候选人比例并不高。第一题int i 1; int j i i;问j是多少。答案是i先取1参与加法i变成2i先变成3再参与加法所以1 3 4i最终是3。这道题考的是自增前后缀的执行顺序。第二题和的区别。一要答出短路性二要答出应用场景短路适合判空保护、条件前置适合位掩码。注意同时也可以作用于整数做位运算而只能作用于布尔值。第三题short和short相加结果是什么类型。答案是int。因为Java会把小于int的类型自动提升到int再计算。这个知识点在写文件解析、协议解析代码时特别重要因为整个字节流里经常是short和byte在运算。第四题a 1 2输出什么。答案是a12。如果要得到a3得写a (1 2)。这题只需要30秒就能回答但能区分出你是否真的理解字符串拼接的执行顺序。第五题2 3等于多少。答案是16。本质是2乘以2的三次方。第六题如何在不使用Math库的情况下判断一个数是不是奇数。正确写法是(n 1) 1。但要注意负数Java里负奇数的低位也是1所以对于负数这样判断也正确。如果用n % 2 1判断负奇数会出错因为-3 % 2等于-1和1比较为false。很多人的代码里用n % 2 1判断奇数在负数场景下会漏判这是我见过的一个真实线上bug。6.2 线上排查式问题看一行表达式知道错在哪里除了背题实际工作中更看重排查能力。我总结过几个典型的“表达式定位法”写在这里供参考。第一类空指针。看到if (a ! null a.getX() 0)这种等于是标准安全写法。如果排查时空指针还发生在后面那大概率不是表达式问题而是前面的条件逻辑本身不成立或者有人把误写成。第二类边界溢出。如果一段计算金额的逻辑偶尔出现巨大负数先检查参与运算的变量类型是不是int再看有没有复合赋值隐式强转。比如total price * count;里如果price * count已经超过int范围赋值给更大的total类型也不能避免中间结果的溢出解决办法是把其中一项先转成long比如total (long) price * count;。第三类比较失败。字符串内容比较用了或者浮点数比较用了是两类典型的比较失败原因。排查思路很简单先看比较的对象是什么类型再决定用什么运算符。基本类型且没有精度问题用对象内容用equals浮点用误差范围。6.3 避坑技巧速查表我踩过之后总结的几条经验下面的速查表是长期实操攒出来的照着做能规避大部分运算符相关的问题场景推荐做法不推荐做法判空后访问属性obj ! null obj.getX() 0obj.getX() 0 obj ! null整数除2或乘2n 1面试可用业务代码里直接用移位做乘除可读性差字符串内容比较str.equals(target)str target金额计算BigDecimal或long按分存储double直接累加复合赋值越界用更宽的类型先计算再赋值直接依赖隐式强转不确定优先级加括号赌编译器行为和同事的理解多个自增在同一表达式拆成多行写i i i这种炫技代码位运算混入比较(flags MASK) 1flags MASK 16.4 一点个人经验把表达式当作可读性契约来写最后分享一个我自己的习惯。我写代码时会把表达式当成一份“可读性契约”凡是自己看一眼不能立刻说出执行结果的表达式就立即拆开重写。这不是说我不信任自己对运算符的理解而是说代码最终是给人读的不能为了少写两行让后面维护的人猜谜。写项目三年以上的同学应该都有体会真正让人头疼的永远不是if、else逻辑而是那些看起来“很简洁”但实际要求读者大脑预编译的表达式。反过来如果你能在代码审查里敏锐指出某个表达式因为优先级问题会算错在面试里能清楚解释i和i的字节码差异这些才是把“基础”学透之后带来的长期红利。运算符与表达式这套东西花一个下午认真过一遍值回票价。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑