×

上篇我们把 8 种基本数据类型和变量声明搞清楚了,数据在变量里就是要拿来用的。这一篇先讲运算符——从加减乘除到自增自减,从短路逻辑到三元表达式,外加面试必考的原码反码补码。这些看似基础的东西,坑比你想的多。

一、算术运算符

先来个全家福:

运算符含义示例结果
+5 + 38
-5 - 32
*5 * 315
/5 / 31(整数除法截断!)
%取模(取余)5 % 32

1.1 整数除法的陷阱

上一篇提过,两个整数相除结果还是整数:

System.out.println(7 / 3);    // 2,不是 2.333...
System.out.println(7.0 / 3);  // 2.3333333333333335
System.out.println(7 / 3.0);  // 2.3333333333333335

只要操作数中有一个是浮点数,Java 就会自动做浮点数除法。

1.2 取模运算符 %

% 得到的是余数,用途很广:

// 判断奇偶
int num = 17;
System.out.println(num % 2 == 0 ? "偶数" : "奇数");  // 奇数

// 对负数取模的结果符号与被除数相同
System.out.println(7 % 3);   // 1
System.out.println(-7 % 3);  // -1
System.out.println(7 % -3);  // 1

🐛 面试高频:a % b 的结果符号与 a 相同。这个问题在实现循环数组、哈希表等场景会出现,有公司把它当笔试题考。

1.3 字符串拼接的 + 号

+ 在 Java 里是个”双重身份”的运算符——两边都是数字时做加法,有一边是 String 时就做拼接:

System.out.println(1 + 2 + "Java");   // "3Java"(从左到右,先算加法)
System.out.println("Java" + 1 + 2);   // "Java12"(遇到 String 后的 + 全变拼接)
System.out.println(1 + (2 + "Java")); // "12Java"(括号改变优先级)

你看出规律了吗?从左到右执行,只要遇到字符串,后面的 + 就变成了拼接符。

二、赋值运算符

最基本的 = 你已经见过了。Java 还提供了复合赋值运算符,把算术和赋值合并:

int x = 10;
x += 5;      // 相当于 x = x + 5  → 15
x -= 3;      // 相当于 x = x - 3  → 12
x *= 2;      // 相当于 x = x * 2  → 24
x /= 4;      // 相当于 x = x / 4  → 6
x %= 3;      // 相当于 x = x % 3  → 0

复合赋值不仅有写法简洁的好处,还有一个隐式的类型转换特性:

byte b = 10;
b = b + 5;     // ❌ 编译报错!b + 5 的结果是 int,不能直接赋给 byte
b += 5;        // ✅ 编译通过!复合赋值自带隐式强转(相当于 b = (byte)(b + 5))

💡 原理E1 op= E2 等价于 E1 = (T)((E1) op (E2)),其中 T 是 E1 的类型。所以 b += 5 会自动强转回 byte。

三、自增自减运算符

不管是写循环还是做计数,++-- 都用得极其频繁。它们的唯一作用就是让变量的值 +1 或 -1。

关键在于前置和后置的区别

int a = 5;
int b = a++;      // 后置++:先使用 a 的当前值(5)赋值给 b,然后 a 自增为 6
System.out.println(a);  // 6
System.out.println(b);  // 5

int c = 5;
int d = ++c;      // 前置++:先将 c 自增为 6,然后把 6 赋值给 d
System.out.println(c);  // 6
System.out.println(d);  // 6

一句话口诀:“先取值后自增”(后置),”先自增后取值”(前置)。

面试里经常这样考:

int i = 0;
for (int j = 0; j < 5; j++) {
    i = i++;
}
System.out.println(i);  // 猜猜结果?

答案是 0。因为 i = i++ 的执行顺序是:先用临时变量保存 i 的值(0)→ i 自增为 1 → 再把临时变量的值(0)赋值给 i。所以循环跑完 i 还是 0。这在新手面试中是个经典陷阱,很多人想当然地认为是 5。

四、关系运算符

关系运算符用来比较两个值的大小或相等关系,结果永远是 boolean(true / false):

int a = 10, b = 20;

System.out.println(a == b);   // false  等于
System.out.println(a != b);   // true   不等于
System.out.println(a > b);    // false  大于
System.out.println(a = b);   // false  大于等于
System.out.println(a <= b);   // true   小于等于

⚠️ 千万别把 == 写成 =

// 经典 bug
int x = 5;
if (x = 10) {   // ❌ 编译报错!Java 中 if 要求 boolean,而 x = 10 的结果是 int
    // ...
}

// 但如果是 boolean 类型…
boolean flag = false;
if (flag = true) {  // 🚨 不会报错!但 flag = true 是赋值表达式,结果就是 true
    // 这段代码永远会执行!这是老 C 程序员常犯的错误
}

Java 比 C 语言更安全——它对条件表达式做了类型检查,int 不能直接当 boolean 用。但 boolean 类型的变量赋值仍然可能踩坑。建议养成习惯:把常量写前面,即 if (true == flag),这样少写一个 = 就会编译报错。

五、逻辑运算符

运算符名称示例结果
&&短路与true && falsefalse
||短路或true || falsetrue
!!truefalse

5.1 短路的含义

“短路”是逻辑运算符最核心的特性——只要结果已经确定,后面的表达式就不再计算

int a = 5, b = 10;

// && 短路:左边为 false,右边不执行
boolean result1 = (a > 10) && (b++ > 0);
System.out.println(b);      // 10(b++ 没被执行!)

// || 短路:左边为 true,右边不执行
boolean result2 = (a  0);
System.out.println(b);      // 10(b++ 仍然没被执行)
面试题实战:

int i = 0, j = 0;
boolean r = (i++ > 0) && (j++ > 0);
System.out.println(i + ", " + j);  // 猜猜? → 1, 0

// i++ > 0 → 0 > 0 → false,i 变成 1
// 因为 && 短路,j++ 没被执行,j 仍然是 0

5.2 & 和 |(不短路版本)

&| 既是位运算符(对整数做位运算),也可以当逻辑运算符(两边是 boolean 时),但不会短路

int a = 5, b = 10;

boolean r1 = (a > 10) & (b++ > 0);  // & 不短路,两边都算
System.out.println(b);              // 11(b++ 执行了)

boolean r2 = (a > 10) && (b++ > 0); // && 短路,右边不算
System.out.println(b);              // 11(没再自增)

日常开发中永远优先用 && 和 ||。用 & 和 | 做逻辑判断的唯一场景是:你确实需要两边都要执行(这种情况极少,且通常用更清晰的代码替代)。

六、三元运算符

Java 里唯一的三目运算符,语法:

类型 变量 = (条件表达式) ? 值1 : 值2;

条件为 true 返回值1,否则返回值2:

int age = 18;
String result = (age >= 18) ? "成年人" : "未成年人";
System.out.println(result);  // 成年人

// 三元运算符可以嵌套,但不建议—可读性太差
int score = 85;
String grade = score >= 90 ? "优秀"
              : score >= 80 ? "良好"
              : score >= 60 ? "及格"
              : "不及格";
System.out.println(grade);   // 良好

三元运算符的本质是一个表达式,有返回值,可以嵌套在另一个表达式中。但嵌套超过两层建议换成 if-else,代码的可读性比代码长度重要得多。

附:原码、反码、补码详解

这一节本来可以单独开一篇,但因为和位运算关系最密切,放在这里一起讲了。很多初学者在这里就放弃了,但其实理解这三个概念对理解 Java 如何处理整数至关重要。

为什么需要这三种码?

计算机里所有数据最终都是二进制——0 和 1。但负数怎么表示?用最高位(符号位)表示正负?那 0 怎么办?减法怎么处理?

工程师们想出了补码的解决方案——用补码表示负数,可以把减法统一成加法,硬件实现就简单多了。

逐个击破

byte 类型(1字节,8位)为例:

概念定义示例(以 -5 为例)
原码最高位符号位(0正1负),其余位是数值的二进制1000 0101
反码正数不变,负数:符号位不变,其余位取反1111 1010
补码正数不变,负数:反码 + 11111 1011

正数的原码、反码、补码是一样的。正数 5:原码 0000 0101 = 反码 0000 0101 = 补码 0000 0101

负数的关系链:

  1. 写出正数的二进制(原码)
  2. 符号位变 1,得到负数的原码
  3. 符号位不变,其他位取反 → 反码
  4. 反码 + 1 → 补码

为什么 byte 的范围是 -128 ~ 127?

看补码的全景图就懂了:

补码
0000 00000
0000 00011
0111 1111127(最大正数)
1000 0000-128(最小负数)
1000 0001-127
1111 1111-1

注意 1000 0000 用来表示 -128,而不是 -0(0 的补码只有一个 0000 0000)。所以 byte 能表示 256 个数:-128 ~ 127。同理 int 的 -2³¹ ~ 2³¹-1 也是这么来的。

补码的实际应用:减法变加法

计算 5 - 3,计算机内部是 5 + (-3)

  5 的补码:0000 0101
 -3 的补码:1111 1101(3 → 0000 0011 → 取反 1111 1100 → +1 → 1111 1101)
  ────────────
  结果:    1 0000 0010

  高位溢出丢弃,得到 0000 0010 = 2 ✅

一个加法器就搞定了减法。这就是补码设计的精妙之处——不需要额外的减法电路。

位运算(你会用上补码知识的地方)

Java 提供了几个位运算符,操作的是整数的二进制位(补码):

运算符名称示例结果(二进制)
&按位与5 & 30000 0101 & 0000 0011 = 0000 0001(1)
|按位或5 | 30000 0101 | 0000 0011 = 0000 0111(7)
^按位异或5 ^ 30000 0101 ^ 0000 0011 = 0000 0110(6)
~按位取反~5~0000 0101 = 1111 1010(-6)
// 常用技巧:用 & 判断奇偶(效率比 % 高一点点)
int num = 7;
System.out.println((num & 1) == 0 ? "偶数" : "奇数");  // 奇数

// 异或的小特性:a ^ b ^ b = a(同数异或两次恢复原值)
int cipher = 42;
int key = 99;
int encrypted = cipher ^ key;   // 加密
int decrypted = encrypted ^ key; // 解密:42
System.out.println(decrypted);  // 42

位运算在日常业务开发中不常用(除非做底层、网络协议、权限控制等),但面试中经常用来考察二进制理解深度。


结语

这一篇我们把 Java 里的运算符都过了一遍:算术运算的整数截断陷阱、赋值运算的隐式强转、自增自减的前后置区别、关系运算符的 == 和 = 的经典 bug、逻辑运算的短路机制、以及三元运算符的灵活运用。最后还深入了一把它底层的原码反码补码和位运算——以后你看到 -128 时就知道它从哪来的了。

下一篇我们来啃流程控制与分支循环语句——if-else、switch、for、while,这些代码逻辑的骨架。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章