90%人踩过的坑!Java运算符优先级与类型转换的终极避坑指南!
本文将全面介绍Java中的各种运算符,从基础算术运算到位操作符,再到Java特有的运算符,每个概念都配有实际代码示例和对比分析。
一、运算符基础概念
1.1 什么是运算符
运算符是用来对操作数进行特定操作的符号。在表达式 3 + 4 中,3 和 4 是操作数,+ 是运算符。
1.2 运算符分类
分类 | 运算符示例 | 说明 |
算术运算符 | +, -, *, /, % | 基本数学运算 |
关系运算符 | >, <, ==, != | 比较操作数关系 |
逻辑运算符 | &&, ||, ! | 布尔逻辑运算 |
位运算符 | &, |, ^, ~ | 二进制位操作 |
赋值运算符 | =, +=, -= | 赋值操作 |
其他运算符 | ?:, instanceof | 条件、类型检查 |
我把运算符分成6大类,用一句话帮你记住:
“算关逻位赋条”(算术、关系、逻辑、位运算、赋值、条件)
二、算术运算符
算术运算符用于执行基本数学运算。
想象你小时候学数学:
- + 是加法(1+1=2)
- - 是减法(5-3=2)
- × 是乘法(2×3=6)
- ÷ 是除法(6÷2=3)
Java里的运算符也是干这些事的,只是写法有点小变化:
- * 代替了 ×(2*3=6)
- / 代替了 ÷(6/2=3)
2.1 基本算术运算符
public class ArithmeticOperators {
public static void main(String[] args) {
int a = 10;
int b = 3;
System.out.println("a + b = " + (a + b)); // 13 加
System.out.println("a - b = " + (a - b)); // 7 减
System.out.println("a * b = " + (a * b)); // 30 乘
System.out.println("a / b = " + (a / b)); // 3 (整数除法)
System.out.println("a % b = " + (a % b)); // 1 取余(10除以3余1)
double c = 10.0;
System.out.println("c / b = " + (c / b)); // 3.333... (浮点除法)
}
}
2.2 一元运算符
public class UnaryOperators {
public static void main(String[] args) {
int x = 5;
// 正负号
System.out.println(+x); // 5
System.out.println(-x); // -5
// 自增自减
System.out.println(x++); // 5 (后置,先使用后增加)
System.out.println(x); // 6
System.out.println(++x); // 7 (前置,先增加后使用)
System.out.println(x--); // 7 (后置,先使用后减少)
System.out.println(x); // 6
System.out.println(--x); // 5 (前置,先减少后使用)
}
}
2.3 算术运算符优先级
优先级 | 运算符 | 结合性 |
高 | ++, – (后置) | 左→右 |
+, - (一元), ++, – (前置) | 右→左 | |
*, /, % | 左→右 | |
低 | +, - (二元) | 左→右 |
2.4 算术运算符的坑与技巧
1)、整数除法的坑(新手必踩)
// 你以为的除法
System.out.println(5 / 2); // 输出啥?2.5?错!输出2!
// 正确做法(至少有一个浮点数)
System.out.println(5 / 2.0); // 输出2.5
System.out.println((double)5 / 2); // 输出2.5
分析:Java中两个整数相除结果还是整数,小数部分直接被砍掉(不是四舍五入)。就像你买5个苹果分给2个人,每人得2个,剩下的1个不分了。
2)、自增自减的玄机(笔试常考)
int a = 5;
int b = a++; // 先把a的值赋给b,然后a自增
System.out.println("a=" + a + ", b=" + b); // a=6, b=5
int c = 5;
int d = ++c; // 先c自增,再把值赋给d
System.out.println("c=" + c + ", d=" + d); // c=6, d=6
记忆口诀:
- i++:先用后加(先做事再吃饭)
- ++i:先加后用(先吃饭再做事)
3)、取余运算的妙用
// 判断奇偶
int num = 7;
if (num % 2 == 0) {
System.out.println("偶数");
} else {
System.out.println("奇数"); // 输出这个
}
// 限制范围(比如游戏中的循环地图)
int position = 10;
int mapSize = 8;
int actualPosition = position % mapSize; // 2
三、关系运算符
关系运算符用于比较两个值,返回布尔结果。
public class RelationalOperators {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("a == b: " + (a == b)); // false
System.out.println("a != b: " + (a != b)); // true
System.out.println("a > b: " + (a > b)); // false
System.out.println("a < b: " + (a < b)); // true
System.out.println("a >= b: " + (a >= b)); // false
System.out.println("a <= b: " + (a <= b)); // true
// 对象比较
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println("s1 == s2: " + (s1 == s2)); // false (比较引用)
System.out.println("s1.equals(s2): " + s1.equals(s2)); // true (比较内容)
}
}
浮点数比较的坑
// 错误示范
double d1 = 0.1 + 0.2;
double d2 = 0.3;
System.out.println(d1 == d2); // false!惊不惊喜?
// 正确做法
final double EPSILON = 1e-10; // 定义一个很小的误差范围
System.out.println(Math.abs(d1 - d2) < EPSILON); // true
重要提醒:
1、字符串比较要用equals(),==比较的是内存地址!
2、计算机用二进制存小数会有精度误差,就像你用尺子量东西总有毫米级的误差一样。
四、逻辑运算符:真假判官
逻辑运算符用于组合布尔表达式。
4.1 基本逻辑运算符
public class LogicalOperators {
public static void main(String[] args) {
boolean a = true;
boolean b = false;
System.out.println("a && b: " + (a && b)); // false
System.out.println("a || b: " + (a || b)); // true
System.out.println("!a: " + (!a)); // false
// 短路演示
int x = 5;
boolean result = (x < 10) || (x++ > 0);
System.out.println("result: " + result); // true
System.out.println("x: " + x); // 5 (x++未执行)
}
}
4.2 逻辑运算符对比
运算符 | 名称 | 特点 | 示例 |
&& | 逻辑与 | 短路,第一个false则不再计算第二个 | false && expr 不计算expr |
|| | 逻辑或 | 短路,第一个true则不再计算第二个 | true || expr 不计算expr |
! | 逻辑非 | 一元运算符,反转布尔值 | !true → false |
& | 非短路与 | 总是计算两个操作数 | false & expr 计算expr |
| | 非短路或 | 总是计算两个操作数 | true | expr 计算expr |
^ | 逻辑异或 | 相同为false,不同为true | true ^ false → true |
4.3 短路特性(超实用!)
// 用户输入校验
String input = null;
// 如果用单个&会报空指针,因为两边都会计算
if (input != null && input.length() > 0) {
System.out.println("有效输入");
} else {
System.out.println("无效输入"); // 输出这个
}
短路原理:
- &&:左边为false时,右边不计算
- ||:左边为true时,右边不计算
就像你找对象:
- 要求"高富帅"(条件1 && 条件2 && 条件3),第一条不满足后面就不用问了
- 要求"会做饭或会唱歌"(条件1 || 条件2),第一条满足就直接通过
五、位运算符
位运算符直接操作整数的二进制位。
5.1 基本位运算符
public class BitwiseOperators {
public static void main(String[] args) {
int a = 5; // 二进制: 0101
int b = 3; // 二进制: 0011
System.out.println("a & b: " + (a & b)); // 0001 → 1 (按位与)
System.out.println("a | b: " + (a | b)); // 0111 → 7 (按位或)
System.out.println("a ^ b: " + (a ^ b)); // 0110 → 6 (按位异或)
System.out.println("~a: " + (~a)); // 111...1010 → -6 (按位取反)
// 移位运算符
int c = 8; // 二进制: 1000
System.out.println("c << 1: " + (c << 1)); // 10000 → 16 (左移)
System.out.println("c >> 1: " + (c >> 1)); // 0100 → 4 (带符号右移)
System.out.println("c >>> 1: " + (c >>> 1)); // 0100 → 4 (无符号右移)
int d = -8;
System.out.println("d >> 1: " + (d >> 1)); // 保持符号位
System.out.println("d >>> 1: " + (d >>> 1)); // 不保持符号位
}
}
5.2 高效计算技巧
// 乘以2的n次方
int num = 3;
int result = num << 2; // 3*4=12
// 除以2的n次方
result = num >> 1; // 3/2=1
// 判断奇偶(比%更快)
boolean isOdd = (num & 1) == 1; // true
// 交换两个数(不用临时变量)
int x = 5, y = 10;
x ^= y;
y ^= x;
x ^= y; // x=10, y=5
5.3 位运算符应用场景
- 权限控制:用位掩码表示不同权限
- 高效计算:某些数学运算用位操作更高效
- 加密算法:许多加密算法依赖位操作
- 优化存储:用位存储布尔值节省空间
/**
* 该类实现了基于位运算的权限管理系统
* 使用位掩码技术来表示和操作不同的权限
*/
public class Permission {
// 权限常量定义,使用2的幂次方值(1,2,4,8...)确保每个权限对应二进制的一位
/**
* 读权限 - 二进制表示为0001
*/
public static final int READ = 1; // 0001
/**
* 写权限 - 二进制表示为0010
*/
public static final int WRITE = 2; // 0010
/**
* 执行权限 - 二进制表示为0100
*/
public static final int EXECUTE = 4; // 0100
/**
* 存储当前权限的位掩码
* 每一位代表一个权限,通过位运算来操作
*/
private int permissions;
/**
* 设置权限位掩码(会覆盖原有权限)
* @param permissions 新的权限位掩码
*/
public void setPermissions(int permissions) {
this.permissions = permissions;
}
/**
* 添加一个权限(不会影响其他权限)
* 使用位或运算(|)来添加权限
* @param permission 要添加的权限(READ, WRITE 或 EXECUTE)
*/
public void addPermission(int permission) {
// 位或运算:只要有一个为1,结果位就为1
permissions |= permission;
}
/**
* 移除一个权限(不会影响其他权限)
* 使用位与运算(&)和位非运算(~)来移除权限
* @param permission 要移除的权限(READ, WRITE 或 EXECUTE)
*/
public void removePermission(int permission) {
// 1. 先对权限取反,例如0010变成1101
// 2. 然后与原权限做与运算,将指定位置0
permissions &= ~permission;
}
/**
* 检查是否拥有某个权限
* @param permission 要检查的权限(READ, WRITE 或 EXECUTE)
* @return 如果拥有该权限返回true,否则返回false
*/
public boolean hasPermission(int permission) {
// 位与运算:如果结果等于要检查的权限,说明对应位都是1
return (permissions & permission) == permission;
}
/**
* 主方法,演示权限类的使用
*/
public static void main(String[] args) {
Permission file = new Permission();
// 添加读和写权限
file.addPermission(READ);
file.addPermission(WRITE);
// 检查权限
System.out.println("Can read? " + file.hasPermission(READ)); // true
System.out.println("Can execute? " + file.hasPermission(EXECUTE)); // false
// 移除写权限
file.removePermission(WRITE);
System.out.println("Can write? " + file.hasPermission(WRITE)); // false
}
}
六、赋值运算符
赋值运算符用于给变量赋值。
6.1 基本赋值运算符
public class AssignmentOperators {
public static void main(String[] args) {
// 简单赋值
int a = 10;
// 复合赋值
a += 5; // 等价于 a = a + 5
System.out.println("a += 5: " + a); // 15
a -= 3; // a = a - 3
System.out.println("a -= 3: " + a); // 12
a *= 2; // a = a * 2
System.out.println("a *= 2: " + a); // 24
a /= 6; // a = a / 6
System.out.println("a /= 6: " + a); // 4
a %= 3; // a = a % 3
System.out.println("a %= 3: " + a); // 1
// 位运算复合赋值
int b = 5;
b &= 3; // b = b & 3 (0101 & 0011 = 0001)
System.out.println("b &= 3: " + b); // 1
b |= 6; // b = b | 6 (0001 | 0110 = 0111)
System.out.println("b |= 6: " + b); // 7
b ^= 2; // b = b ^ 2 (0111 ^ 0010 = 0101)
System.out.println("b ^= 2: " + b); // 5
b <<= 1; // b = b << 1 (0101 << 1 = 1010)
System.out.println("b <<= 1: " + b); // 10
b >>= 2; // b = b >> 2 (1010 >> 2 = 0010)
System.out.println("b >>= 2: " + b); // 2
}
}
6.2 赋值运算符特点
- 复合赋值运算符会自动进行类型转换
- 比简单赋值更简洁,有时性能略好
- 可以避免重复计算同一表达式
int[] arr = new int[10];
int index = 0;
// 复合赋值可以避免重复计算arr.length
for (int i = 0; i < arr.length; i++) {
arr[i] = index += 2; // 比 index = index + 2 更简洁
}
七、条件运算符(三元运算符)
条件运算符是Java中唯一的三元运算符。
public class ConditionalOperator {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 基本用法
int max = (a > b) ? a : b;
System.out.println("Max: " + max); // 20
// 嵌套使用
int x = 10, y = 20, z = 30;
int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
System.out.println("Largest: " + largest); // 30
// 返回不同类型
Object obj = (a < b) ? "a is smaller" : (b - a);
System.out.println("obj: " + obj); // "a is smaller"
// 实用示例:检查空值
String input = null;
String display = (input != null) ? input : "Default Value";
System.out.println("display: " + display); // "Default Value"
}
}
八、instanceof运算符
instanceof用于检查对象是否是特定类的实例。
public class InstanceofOperator {
static class Animal {}
static class Dog extends Animal {}
static class Cat extends Animal {}
public static void main(String[] args) {
Animal animal = new Dog();
System.out.println("animal instanceof Animal: " + (animal instanceof Animal)); // true
System.out.println("animal instanceof Dog: " + (animal instanceof Dog)); // true
System.out.println("animal instanceof Cat: " + (animal instanceof Cat)); // false
System.out.println("animal instanceof Object: " + (animal instanceof Object)); // true
// 与null检查
Animal nullAnimal = null;
System.out.println("nullAnimal instanceof Animal: " + (nullAnimal instanceof Animal)); // false
// 接口检查
String str = "Hello";
System.out.println("str instanceof CharSequence: " + (str instanceof CharSequence)); // true
// 数组检查
int[] arr = new int[5];
System.out.println("arr instanceof int[]: " + (arr instanceof int[])); // true
System.out.println("arr instanceof Object: " + (arr instanceof Object)); // true
}
}
九、运算符优先级与结合性
9.1 完整运算符优先级表
优先级 | 运算符 | 描述 | 结合性 |
1 | () | 方法调用 | 左→右 |
[] | 数组下标 | ||
. | 成员访问 | ||
-> | Lambda表达式 | ||
2 | ++ – | 后置自增/自减 | 左→右 |
3 | ++ – | 前置自增/自减 | 右→左 |
+ - | 一元加减 | ||
! ~ | 逻辑非、位非 | ||
(type) | 强制类型转换 | ||
new | 对象创建 | ||
4 | * / % | 乘、除、取模 | 左→右 |
5 | + - | 加、减 | 左→右 |
6 | << >> >>> | 移位 | 左→右 |
7 | < <= > >= | 关系比较 | 左→右 |
instanceof | 类型比较 | ||
8 | == != | 相等比较 | 左→右 |
9 | & | 按位与 | 左→右 |
10 | ^ | 按位异或 | 左→右 |
11 | | | 按位或 | 左→右 |
12 | && | 逻辑与 | 左→右 |
13 | || | 逻辑或 | 左→右 |
14 | ?: | 条件运算符 | 右→左 |
15 | = += -= etc. | 赋值运算符 | 右→左 |
9.2 优先级示例
public class OperatorPrecedence {
public static void main(String[] args) {
int a = 10, b = 5, c = 1;
int result = a + b * c; // 等价于 a + (b * c)
System.out.println("result: " + result); // 15
boolean logicalResult = a > b && b < c; // 等价于 (a > b) && (b < c)
System.out.println("logicalResult: " + logicalResult); // false
int bitwiseResult = a & b | c; // 等价于 (a & b) | c
System.out.println("bitwiseResult: " + bitwiseResult); // 1
// 使用括号明确优先级
int explicitResult = (a + b) * c;
System.out.println("explicitResult: " + explicitResult); // 15
}
}
十、Java运算符最佳实践
10.1 避免常见陷阱
- 整数除法:5 / 2 结果是2,不是2.5
- 浮点数比较:避免直接==比较浮点数,使用差值比较
- 自增自减副作用:避免在复杂表达式中使用
- 短路逻辑:利用&&和||的短路特性优化代码
- 运算符优先级混淆:不确定时使用括号明确优先级
10.2 性能考虑
- 位运算通常比算术运算快
- 复合赋值可能比简单赋值效率略高
- 避免在循环条件中重复计算
10.3 可读性建议
- 复杂表达式拆分成多行
- 使用括号明确意图
- 避免过度使用三元运算符嵌套
- 为特殊位操作添加注释
// 好例子:清晰易读
int total = (basePrice * quantity) + (taxRate * basePrice);
boolean isValid = (input != null) && (input.length() > 0);
// 坏例子:过于复杂
int result=a*b+c/d-e%f+g<<h&i|j^k;
十一、Java运算符高级应用
11.1 使用位运算实现标志位
public class BitFlags {
public static final int FLAG_A = 1 << 0; // 0001
public static final int FLAG_B = 1 << 1; // 0010
public static final int FLAG_C = 1 << 2; // 0100
public static final int FLAG_D = 1 << 3; // 1000
private int flags;
public void setFlag(int flag) {
flags |= flag;
}
public void clearFlag(int flag) {
flags &= ~flag;
}
public boolean hasFlag(int flag) {
return (flags & flag) == flag;
}
public void toggleFlag(int flag) {
flags ^= flag;
}
public static void main(String[] args) {
BitFlags bf = new BitFlags();
bf.setFlag(FLAG_A);
bf.setFlag(FLAG_C);
System.out.println("Has FLAG_A: " + bf.hasFlag(FLAG_A)); // true
System.out.println("Has FLAG_B: " + bf.hasFlag(FLAG_B)); // false
bf.toggleFlag(FLAG_A);
System.out.println("Has FLAG_A after toggle: " + bf.hasFlag(FLAG_A)); // false
}
}
11.2 高效数学运算技巧
public class MathTricks {
public static void main(String[] args) {
// 判断奇偶
int num = 15;
boolean isEven = (num & 1) == 0;
System.out.println(num + " is even? " + isEven); // false
// 交换两个数不使用临时变量
int a = 5, b = 10;
a ^= b;
b ^= a;
a ^= b;
System.out.println("a: " + a + ", b: " + b); // a:10, b:5
// 计算绝对值
int x = -25;
int abs = (x ^ (x >> 31)) - (x >> 31);
System.out.println("abs(" + x + ") = " + abs); // 25
// 判断是否为2的幂
int n = 16;
boolean isPowerOfTwo = (n & (n - 1)) == 0 && n != 0;
System.out.println(n + " is power of two? " + isPowerOfTwo); // true
}
}
11.3 运算符重载的替代方案
Java不支持运算符重载,但可以通过方法实现类似功能:
public class ComplexNumber {
private final double real;
private final double imaginary;
public ComplexNumber(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
public ComplexNumber add(ComplexNumber other) {
return new ComplexNumber(this.real + other.real,
this.imaginary + other.imaginary);
}
public ComplexNumber subtract(ComplexNumber other) {
return new ComplexNumber(this.real - other.real,
this.imaginary - other.imaginary);
}
@Override
public String toString() {
return real + " + " + imaginary + "i";
}
public static void main(String[] args) {
ComplexNumber c1 = new ComplexNumber(2, 3);
ComplexNumber c2 = new ComplexNumber(4, 5);
ComplexNumber sum = c1.add(c2);
ComplexNumber diff = c1.subtract(c2);
System.out.println("Sum: " + sum); // 6.0 + 8.0i
System.out.println("Difference: " + diff); // -2.0 + -2.0i
}
}
十二、常见问题解答
Q1:i++和++i到底哪个更快?
实测:在现代JVM上几乎没有区别!编译器会优化。但为了代码可读性:
- 单独使用:用i++
- 需要返回值:根据业务需求选择
Q2:为什么128 == 128返回false?
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
Integer c = 127;
Integer d = 127;
System.out.println(c == d); // true
原因:Integer缓存了-128到127的值,超出范围会new新对象。应该用equals()比较!
Q3:如何避免运算符优先级问题?
提醒:
- 记不住优先级?加括号!
- 复杂表达式拆分成多行
- 团队统一代码风格
// 迷惑写法
int result = a << 1 + b * c >> 2;
// 清晰写法
int temp = b * c;
int shifted = a << (1 + temp);
result = shifted >> 2;
十三、进阶知识:JVM如何处理运算符?
13.1 字节码层面看运算
用javap -c查看编译后的字节码:
int a = 5;
int b = a++ + ++a;
对应的字节码:
0: iconst_5 // 把5压入栈
1: istore_1 // 存储到变量a
2: iload_1 // 加载a的值(5)
3: iinc 1, 1 // a自增(a=6)
6: iinc 1, 1 // a再自增(a=7)
9: iload_1 // 加载a的值(7)
10: iadd // 栈顶两个值相加(5+7)
11: istore_2 // 存储到b
13.2 性能优化建议
- 循环中的运算:把不变的计算提到循环外
// 差
for (int i = 0; i < list.size() * 2; i++) {...}
// 好
int limit = list.size() * 2;
for (int i = 0; i < limit; i++) {...}
- 避免重复计算:
// 差
double area = Math.PI * r * r;
double circumference = 2 * Math.PI * r;
// 好
final double piR = Math.PI * r;
double area = piR * r;
double circumference = 2 * piR;
总结
- 基础要牢:算术、关系、逻辑三大件必须烂熟于心
- 避坑指南:整数除法要小心浮点比较用误差范围字符串比较用equals
- 高级技巧:位运算处理标志位利用短路特性优化代码三目运算符简化if-else
- 最佳实践:不确定优先级就加括号复杂表达式拆解写适当使用复合赋值
这些 Java 运算符小精灵们,在代码的世界里各司其职,有时会让你的代码顺利运行,有时也会调皮地给你制造一些小麻烦,但正是它们的存在,让编程变得充满乐趣!