"大翔在飞翔科技代码评审会上敲着白板,圈出小崔写的一行条件判断:
if (a & b == 0)。'你期望的是(a & b) == 0,但Java执行的却是a & (b == 0)——因为==的优先级高于&。'小崔擦了擦汗:'我以为运算符优先级是常识就没查表……'白歌点头:'优先级不是靠猜的——42种操作符排成15个等级,一张表比你三年的直觉都靠谱。'"
运算符优先级
概述
当表达式中同时出现多个运算符时,优先级决定了"谁先算"。Java SE 8定义了15个优先级等级,从高到低覆盖全部42种操作符。同级运算符按结合性决定求值方向(左结合或右结合)。
完整运算符优先级表(JDK 8)
记忆技巧:优先级数字越小,实际越先执行。1级最高,15级最低。
| 优先级 | 运算符 | 名称 | 结合性 |
|---|---|---|---|
| 1 | () [] . | 圆括号、数组下标、成员访问 | 左 |
| 2 | ++ -- + - ~ ! (type) | 前/后自增自减、正负号、按位取反、逻辑非、强制类型转换 | 右 |
| 3 | * / % | 乘、除、取模 | 左 |
| 4 | + - | 加、减 | 左 |
| 5 | << >> >>> | 左移、带符号右移、无符号右移 | 左 |
| 6 | < <= > >= instanceof | 小于、小于等于、大于、大于等于、类型判断 | 左 |
| 7 | == != | 等于、不等于 | 左 |
| 8 | & | 按位与(或不短路逻辑与) | 左 |
| 9 | ^ | 按位异或 | 左 |
| 10 | | | 按位或(或不短路逻辑或) | 左 |
| 11 | && | 短路逻辑与 | 左 |
| 12 | || | 短路逻辑或 | 左 |
| 13 | ? : | 三元条件运算符 | 右 |
| 14 | = += -= *= /= %= &= |= ^= <<= >>= >>>= | 赋值运算符(含复合) | 右 |
| 15 | -> | Lambda箭头(Java 8新增) | —— |
说明:优先级15的
->(Lambda箭头)是Java 8新增的表达式操作符,用于Lambda表达式。虽然本章聚焦传统运算符,但需知晓它处于优先级最低级别——几乎任何表达式都比->先求值。
深度原理分析
一、结合性 —— 同级运算符谁先算?
当表达式中出现多个同级运算符时,结合性决定求值方向:
| 结合性 | 方向 | 示例 | 等价于 |
|---|---|---|---|
| 左结合 | 从左到右 | a - b - c | (a - b) - c |
| 右结合 | 从右到左 | a = b = c | a = (b = c) |
Java中大多数运算符是左结合。右结合的只有三类:
- 一元运算符(
++、--、+、-、~、!、(type)) - 三元条件运算符(
? :) - 赋值运算符(
=、+=、-=……)
// 左结合示例
int result = 100 / 10 / 2; // (100 / 10) / 2 = 5,而非 100 / (10 / 2) = 20
System.out.println(result); // 5
// 右结合示例
int a, b, c;
a = b = c = 10; // a = (b = (c = 10))
二、&& 和 || 之间的优先级关系
&& 优先级(11级)高于 ||(12级)。这使得 a || b && c 被解析为 a || (b && c),恰好符合布尔代数中的标准约定——AND 优先于 OR(类似算术中乘法优先于加法)。
boolean t = true, f = false;
// && 优先级高于 || : a || b && c 等价于 a || (b && c)
boolean r1 = t || f && t; // t || (f && t) = t || f = true
boolean r2 = (t || f) && t; // (t || f) && t = t && t = true
// 这两例结果相同,换一个能看出差异的:
boolean r3 = f || t && f; // f || (t && f) = f || f = false
boolean r4 = (f || t) && f; // (f || t) && f = t && f = false
完整示例一:飞翔科技综合表达式求值——优先级陷阱
场景
白歌在代码审查中出了一道题给技术部新人——解析复杂的表达式求值过程,理解优先级和结合性的双重规则。
/**
* 飞翔科技 —— 运算符优先级实战
* 理解优先级与结合性
*/
public class OperatorPrecedenceDemo {
public static void main(String[] args) {
System.out.println("===== 飞翔科技 运算符优先级实验 feixiang.net =====\n");
// --- 实验1:优先级 —— & 与 == 的经典陷阱 ---
System.out.println("--- 实验1: 优先级陷阱 ---");
int a = 5; // 0101
int b = 3; // 0011
// 错误理解:先 (a & b) 再 == 0
// 实际执行:先 (b == 0) 再 & a(因为 == 优先级高于 &)
// boolean r1_wrong = a & b == 0; // 等价于 a & (b == 0) → 编译错误!
// 正确写法
boolean r1_right = (a & b) == 0;
System.out.println("(5 & 3) == 0 → " + r1_right);
// --- 实验2:结合性 —— 除法的左结合 ---
System.out.println("\n--- 实验2: 结合性 ---");
int n1 = 100 / 10 / 2;
int n2 = 100 / (10 / 2);
System.out.println("100 / 10 / 2 = " + n1 + "(左结合:(100/10)/2)");
System.out.println("100 / (10/2) = " + n2 + "(括号改变结合)");
// --- 实验3:&& 高于 || ---
System.out.println("\n--- 实验3: && 高于 || ---");
boolean t = true, f = false;
boolean r5a = t || f && t; // t || (f && t) = t || f = true
boolean r5b = (t || f) && t; // (t || f) && t = t && t = true
System.out.println("true || false && true = " + r5a + "(等价于 true || (false && true))");
System.out.println("(true || false) && true = " + r5b);
boolean r5c = f || t && f; // f || (t && f) = f || f = false
boolean r5d = (f || t) && f; // (f || t) && f = t && f = false
System.out.println("false || true && false = " + r5c + "(等价于 false || (true && false))");
System.out.println("(false || true) && false = " + r5d);
// --- 实验4:<< 与 + 的优先级 ---
System.out.println("\n--- 实验4: << 与 + 的优先级 ---");
int shift1 = 1 << 2 + 3; // + 优先于 <<:1 << (2+3) = 1 << 5 = 32
int shift2 = (1 << 2) + 3; // 括号明: (1<<2) + 3 = 4 + 3 = 7
System.out.println("1 << 2 + 3 = " + shift1 + "(等价于 1 << (2+3) = 32)");
System.out.println("(1 << 2) + 3 = " + shift2 + "(等价于 4 + 3 = 7)");
}
}
运行输出:
===== 飞翔科技 运算符优先级实验 feixiang.net =====
--- 实验1: 优先级陷阱 ---
(5 & 3) == 0 → false
--- 实验2: 结合性 ---
100 / 10 / 2 = 5(左结合:(100/10)/2)
100 / (10/2) = 20(括号改变结合)
--- 实验3: && 高于 || ---
true || false && true = true(等价于 true || (false && true))
(true || false) && true = true
false || true && false = false(等价于 false || (true && false))
(false || true) && false = false
--- 实验4: << 与 + 的优先级 ---
1 << 2 + 3 = 32(等价于 1 << (2+3) = 32)
(1 << 2) + 3 = 7(等价于 4 + 3 = 7)
完整示例二:飞翔商城价格表达式——显式优先级
场景
黄俪在飞翔商城开发促销规则引擎,深刻体会到——没有优先级知识,任何复杂表达式都是一颗定时炸弹。
/**
* 飞翔商城 —— 价格表达式中的优先级实战
*/
public class PriceExpressionPrecedence {
public static void main(String[] args) {
System.out.println("===== 飞翔商城 价格表达式优先级 feixiang.net =====\n");
float basePrice = 188.88f;
int quantity = 5;
boolean isVip = true;
float discount = 0.15f;
float shipping = 10.0f;
// 复杂表达式:最终价格 = 原价×数量×(1-折扣) + (VIP?0:运费)
// 括号明确优先级,每一步都可追溯
float finalPrice = basePrice * quantity * (1 - discount) + (isVip ? 0 : shipping);
System.out.println("最终价格: ¥" + finalPrice);
// --- 常见优先级错误 ---
System.out.println("\n===== 常见优先级错误 =====");
// 错误写法1:位运算与关系运算混用
int flags = 0b1010;
// ❌ 错误:flags & 0b0010 == 0b0010 → flags & (0b0010 == 0b0010) → 编译错误
// ✅ 正确:(flags & 0b0010) == 0b0010
boolean hasBit1 = (flags & 0b0010) == 0b0010;
System.out.println("(0b1010 & 0b0010) == 0b0010 → " + hasBit1 + "(用括号强制优先级)");
// 错误写法2:三元嵌套无括号
int salary = 8888;
String level = (salary >= 10000) ? "高级"
: (salary >= 5000) ? "中级"
: "初级";
System.out.println("薪资等级(括号明确): " + level);
// 错误写法3:赋值与比较混淆
boolean flag = false;
// if (flag = true) // ⚠️ 编译通过!永远为true
// 正确做法:直接使用变量名
if (flag) {
System.out.println("这不会执行,因为flag=false");
}
System.out.println("赋值vs比较的坑:if(flag=true) 编译通过且永远为true!");
}
}
运行输出:
===== 飞翔商城 价格表达式优先级 feixiang.net =====
最终价格: ¥802.74
===== 常见优先级错误 =====
(0b1010 & 0b0010) == 0b0010 → true(用括号强制优先级)
薪资等级(括号明确): 中级
赋值vs比较的坑:if(flag=true) 编译通过且永远为true!
易错场景
反例一:位运算与比较运算符混用(Java最著名的优先级陷阱)
// ❌ 这是Java中最著名的优先级陷阱之一!
int flags = 0b1010; // bit1=1, bit3=1
if (flags & 0b0001 == 0) {
// 期望:(flags & 0b0001) == 0 → 检测 bit0 是否为 0
// 实际:flags & (0b0001 == 0) → flags & false → 编译错误!
System.out.println("bit0 为 0");
}
纠正:
// ✅ 正确:括号明确优先级
if ((flags & 0b0001) == 0) {
System.out.println("bit0 为 0");
}
记忆口诀:
==和!=的优先级高于&、^、|。位运算与比较运算混用时,必须加括号。
反例二:<< 与 + 的优先级混淆
// ❌ 错误:以为 << 优先级高于 +
int result = 1 << 2 + 3; // 期望 (1<<2)+3=7,实际 1<<(2+3)=1<<5=32
System.out.println(result); // 32!
原因:+ 的优先级(4级)高于 <<(5级),所以 2 + 3 先算。
纠正:
// ✅ 正确:用括号明确意图
int result = (1 << 2) + 3; // 7
反例三:赋值与比较的混淆(Java新手头号陷阱)
// ❌ 经典错误:将 = 误写为 ==
boolean flag = false;
if (flag = true) { // 编译通过!flag 被赋值为 true,表达式值为 true,永远进入
System.out.println("这总会执行!"); // 并非本意!
}
纠正:
// ✅ 防范方案
boolean flag = false;
if (flag) { // 直接写变量名,最简洁
// ...
}
// 或者习惯于常量在左:if (true == flag) —— 若写成 true = flag 会编译报错
面试考点
Q1:Java运算符优先级一共有多少级?最高和最低分别是什么?
Java SE 8共15级优先级(不考虑Lambda箭头为14级)。最高:
()[].(1级,括号、数组下标、成员访问)。最低:=+=等赋值运算符(14级)和->Lambda箭头(15级)。实际上日常编码最需要记住的三组:①* / %>+ -;② 关系运算符><>=<=>==!=>&>^>|>&&>||;③ 赋值运算符优先级极低。
Q2:a + b * c 和 (a + b) * c 哪位先被执行?为什么?
a + b * c中b * c先执行,因为*优先级(3级)高于+(4级)。而(a + b) * c中a + b先执行,因为括号优先级最高(1级),覆盖了默认优先级规则。
Q3:&& 和 || 之间谁的优先级更高?为什么要这样设计?
&&优先级(11级)高于||(12级)。这样设计使得a || b && c被解析为a || (b && c),这恰好符合布尔代数中的标准约定——AND 优先于 OR(类似算术中乘法优先于加法)。这种设计使得不加括号时表达式更符合数学直觉。
Q4:为什么很多编码规范建议"在复杂表达式中加括号"?
虽然Java有明确的优先级规则,但人的记忆是有限的。超过2种运算符混用时,即使语法正确,阅读者也可能误解意图。加括号使意图明确、无需查表,减少Bug。此外,某些反直觉的优先级(如
<<低于+、&低于==)是老牌陷阱,括号是最简单的防御手段。