运算符
# 03.运算符
# 目录介绍
- 3.1 运算符介绍
- 3.1.1 运算符由来
- 3.1.2 运算符本质
- 3.1.3 常见运算符
- 3.1.4 综合案例:运算符分类速查工具
- 3.1.5 训练题
- 3.2 算术运算符
- 3.2.1 加减乘除
- 3.2.2 取模
- 3.2.3 递增递减
- 3.2.4 字符串拼接
- 3.2.5 综合案例:时间转换计算器
- 3.2.6 算术运算符训练题
- 3.3 赋值运算符
- 3.3.1 赋值案例
- 3.3.2 复合赋值隐含转换
- 3.3.3 赋值运算符训练题
- 3.4 比较运算符
- 3.4.1 比较案例
- 3.4.2 equals和==区别
- 3.4.3 比较运算符训练题
- 3.5 逻辑运算符
- 3.5.1 逻辑非
- 3.5.2 逻辑与
- 3.5.3 逻辑或
- 3.5.4 短路特性
- 3.5.5 综合案例:用户登录验证器
- 3.5.6 逻辑运算符训练题
- 3.6 位运算符
- 3.6.1 综合案例:位运算权限管理器
- 3.6.2 位运算符训练题
- 3.7 运算符优先级
- 3.7.1 综合案例:优先级陷阱检测器
- 3.7.2 训练题
- 3.8 instanceof运算符
- 3.8.1 instanceof介绍
- 3.8.2 基本用法
- 3.8.3 模式匹配(JDK16+)
- 3.8.4 综合案例:多态类型识别器
- 3.8.5 instanceof训练题
- 3.9 三元运算符
- 3.9.1 综合案例:成绩等级评定器
- 3.9.2 训练题
# 3.1 运算符介绍
# 3.1.1 运算符由来
起源:数学与逻辑的抽象。运算符的概念源于数学(+, -, *, /, =)和形式逻辑(&&, ||, !)。
编程语言将这些符号引入,用于操作变量、常量和表达式,执行特定的计算或逻辑功能。
# 3.1.2 运算符本质
运算符的底层原理:运算符在编译后会转化为对应的字节码指令。JVM 提供了专门的算术指令来处理不同数据类型的运算:
| 数据类型 | 加 | 减 | 乘 | 除 | 取模 |
|---|---|---|---|---|---|
| int | iadd | isub | imul | idiv | irem |
| long | ladd | lsub | lmul | ldiv | lrem |
| float | fadd | fsub | fmul | fdiv | frem |
| double | dadd | dsub | dmul | ddiv | drem |
注意:JVM 没有专门的
byte、short、char运算指令,这些类型参与运算时会先提升为 int(使用i2b、i2s等指令转回)。这就是为什么byte + byte结果是int的根本原因。
对比 C++:C++ 中运算符可以被重载(operator+),Java 中不支持运算符重载(唯一的例外是 + 用于字符串拼接,这是语言内置的)。
疑惑:Java 为什么不支持运算符重载?
答疑:Java 的设计哲学是"简单明确"。Gosling 认为 C++ 的运算符重载虽然灵活,但容易被滥用——当你看到 a + b 时,如果运算符被重载,你无法确定它做了什么(可能是连接数据库、发送网络请求...)。Java 只允许 + 用于 String 拼接,保证了运算符的行为是可预测的。
结果展示:这个设计决策有争议。Kotlin 和 Scala 都运行在 JVM 上,但它们都支持运算符重载。Kotlin 用 operator fun 关键字明确标记,比 C++ 更加节制。这说明运算符重载本身不是问题,关键是语言如何约束它的使用。
# 3.1.3 常见运算符
| 运算符类型 | 作用 |
|---|---|
| 算术运算符 | 用于处理四则运算 |
| 赋值运算符 | 用于将表达式的值赋给变量 |
| 比较运算符 | 用于表达式的比较,返回 boolean 值 |
| 逻辑运算符 | 用于根据表达式的值返回 boolean 值 |
| 位运算符 | 用于对二进制位进行操作 |
| 三元运算符 | 简洁的条件表达式 |
| instanceof | 判断对象是否是某个类型的实例 |
# 3.1.4 综合案例:运算符分类速查工具
编写一个程序,接受用户输入的运算符符号,输出该运算符的分类、用途和对应的 JVM 字节码指令。
import java.util.Scanner;
public class OperatorLookup {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个运算符(如 +, ==, &&, >>):");
String op = sc.nextLine().trim();
System.out.println("\n===== 运算符查询结果 =====");
switch (op) {
case "+":
printInfo("算术运算符", "加法 / 字符串拼接", "iadd(int), ladd(long), dadd(double)");
System.out.println(" 特殊说明: + 是Java唯一被\"重载\"的运算符,用于String拼接");
break;
case "-": printInfo("算术运算符", "减法", "isub, lsub, dsub"); break;
case "*": printInfo("算术运算符", "乘法", "imul, lmul, dmul"); break;
case "/": printInfo("算术运算符", "除法", "idiv, ldiv, ddiv"); break;
case "%": printInfo("算术运算符", "取模", "irem, lrem, drem"); break;
case "++": printInfo("算术运算符", "自增", "iinc(局部变量优化)"); break;
case "=": printInfo("赋值运算符", "赋值", "istore, astore等"); break;
case "+=": printInfo("复合赋值运算符", "加后赋值(含隐式转换)", "iadd + i2b/i2s(如需)"); break;
case "==": printInfo("比较运算符", "相等比较", "if_icmpeq(int), if_acmpeq(引用)"); break;
case "!=": printInfo("比较运算符", "不等比较", "if_icmpne(int)"); break;
case "&&": printInfo("逻辑运算符", "短路与", "条件跳转指令组合"); break;
case "||": printInfo("逻辑运算符", "短路或", "条件跳转指令组合"); break;
case "&": printInfo("位运算/逻辑运算", "按位与/非短路与", "iand"); break;
case "|": printInfo("位运算/逻辑运算", "按位或/非短路或", "ior"); break;
case "^": printInfo("位运算符", "按位异或", "ixor"); break;
case "~": printInfo("位运算符", "按位取反", "iconst_m1 + ixor"); break;
case "<<": printInfo("位运算符", "左移", "ishl"); break;
case ">>": printInfo("位运算符", "有符号右移", "ishr"); break;
case ">>>": printInfo("位运算符", "无符号右移(Java特有)", "iushr"); break;
case "instanceof": printInfo("类型判断运算符", "判断对象类型", "instanceof指令"); break;
case "?:": printInfo("三元运算符", "条件表达式", "条件跳转指令组合"); break;
default: System.out.println(" 未收录的运算符: " + op);
}
// 展示运算符分类汇总
System.out.println("\n===== 运算符分类汇总 =====");
String[][] categories = {
{"算术运算符", "+ - * / % ++ --"},
{"赋值运算符", "= += -= *= /= %= &= |= ^= <<= >>= >>>="},
{"比较运算符", "== != > < >= <="},
{"逻辑运算符", "&& || ! & |"},
{"位运算符", "& | ^ ~ << >> >>>"},
{"三元运算符", "? :"},
{"类型运算符", "instanceof"},
};
for (String[] cat : categories) {
System.out.printf(" %-10s: %s%n", cat[0], cat[1]);
}
sc.close();
}
static void printInfo(String category, String usage, String bytecode) {
System.out.println(" 分类: " + category);
System.out.println(" 用途: " + usage);
System.out.println(" 字节码: " + bytecode);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 3.1.5 训练题
填空题:Java 中唯一被"重载"的运算符是 ___,它用于 ___ 拼接。Java 不支持用户自定义运算符重载。
选择题:以下关于运算符底层原理的说法,正确的是?
- A.
byte + byte结果是byteB. JVM 有专门的byte运算指令 C.byte + byte结果是intD. Java 支持运算符重载
- A.
思考题:Java 不支持运算符重载,而 Kotlin、Scala 支持。你认为运算符重载的利弊各是什么?在什么场景下运算符重载是有价值的?(如复数运算、矩阵运算)
# 3.2 算术运算符
作用:用于处理四则运算
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| + | 加 | 10 + 5 | 15 |
| - | 减 | 10 - 5 | 5 |
| * | 乘 | 10 * 5 | 50 |
| / | 除 | 10 / 3 | 3(整数除法) |
| % | 取模 | 10 % 3 | 1 |
| ++ | 自增 | a++ / ++a | |
| -- | 自减 | a-- / --a |
# 3.2.1 加减乘除
public class Main {
public static void main(String[] args) {
int a = 10, b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3(整数除法,截断小数)
// 要得到小数结果,至少一个操作数是浮点类型
System.out.println((double) a / b); // 3.3333...
System.out.println(a / (double) b); // 3.3333...
// 除数不能为0
// System.out.println(a / 0); // ArithmeticException
System.out.println(10.0 / 0); // Infinity(浮点除以0得无穷大)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.2.2 取模
System.out.println(10 % 3); // 1
System.out.println(-10 % 3); // -1(结果符号与被除数一致)
System.out.println(10 % -3); // 1
// Java 浮点数也可以取模(C++ 不行)
System.out.println(10.5 % 3); // 1.5
2
3
4
5
6
取模运算的实际应用:
// 1. 判断奇偶
boolean isEven = (num % 2 == 0);
// 2. 循环数组(环形缓冲区)
int[] buffer = new int[10];
int index = 0;
for (int i = 0; i < 100; i++) {
buffer[index % buffer.length] = i; // 索引在 0-9 之间循环
index++;
}
// 3. 时间转换
int totalSeconds = 3661;
int hours = totalSeconds / 3600; // 1
int minutes = (totalSeconds % 3600) / 60; // 1
int seconds = totalSeconds % 60; // 1
System.out.printf("%02d:%02d:%02d%n", hours, minutes, seconds); // 01:01:01
// 4. 分页计算
int totalItems = 23;
int pageSize = 5;
int totalPages = (totalItems + pageSize - 1) / pageSize; // 5(向上取整)
// 或者:int totalPages = (int) Math.ceil((double) totalItems / pageSize);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.2.3 递增递减
int a = 10;
int b = ++a; // 先加再赋值,a=11, b=11
System.out.println("a=" + a + ", b=" + b);
int c = 10;
int d = c++; // 先赋值再加,c=11, d=10
System.out.println("c=" + c + ", d=" + d);
2
3
4
5
6
7
# 3.2.4 字符串拼接
字符串拼接的底层原理:Java 编译器会将 + 字符串拼接优化为 StringBuilder.append() 调用。例如 "a" + b + "c" 会被编译为 new StringBuilder("a").append(b).append("c").toString()。但在循环中,每次迭代编译器都会创建新的 StringBuilder 对象,因此循环拼接应手动使用 StringBuilder。JDK 9+ 引入了 invokedynamic 指令(StringConcatFactory)进一步优化字符串拼接性能。
Java 中 + 运算符被重载用于字符串拼接:
String name = "张三";
int age = 25;
System.out.println("姓名:" + name + ",年龄:" + age);
// 输出:姓名:张三,年龄:25
// 注意运算顺序
System.out.println(1 + 2 + "abc"); // "3abc"(先算1+2=3,再拼接)
System.out.println("abc" + 1 + 2); // "abc12"(先拼接"abc1",再拼接"abc12")
2
3
4
5
6
7
8
JDK 9+ 字符串拼接优化(invokedynamic):
JDK 9 之前,+ 拼接被编译为 StringBuilder.append() 链。JDK 9 引入了 invokedynamic 指令调用 StringConcatFactory,由 JVM 在运行时选择最优拼接策略(可能是 StringBuilder,也可能是直接字节数组拼接),性能更好且生成的字节码更短。
// JDK 8 编译后
new StringBuilder().append("姓名:").append(name).append(",年龄:").append(age).toString();
// JDK 9+ 编译后(伪代码)
invokedynamic makeConcatWithConstants("姓名:\u0001,年龄:\u0001", name, age)
// JVM 运行时决定最优实现
2
3
4
5
6
# 3.2.5 综合案例:时间转换计算器
编写一个程序,综合运用加减乘除、取模、递增递减和字符串拼接,实现秒数与时分秒的相互转换。
import java.util.Scanner;
public class TimeConverter {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入总秒数:");
int totalSeconds = sc.nextInt();
// 使用除法和取模转换
int hours = totalSeconds / 3600;
int minutes = (totalSeconds % 3600) / 60;
int seconds = totalSeconds % 60;
// 字符串拼接输出
String timeStr = hours + "小时" + minutes + "分" + seconds + "秒";
System.out.println(totalSeconds + "秒 = " + timeStr);
System.out.printf("格式化输出: %02d:%02d:%02d%n", hours, minutes, seconds);
// 递增递减演示:倒计时
System.out.println("\n===== 倒计时演示(最后5秒) =====");
int countdown = 5;
while (countdown > 0) {
System.out.println(" " + countdown-- + "..."); // 后置递减
}
System.out.println(" 时间到!");
// 整数除法 vs 浮点除法对比
System.out.println("\n===== 除法对比 =====");
int a = 7, b = 2;
System.out.println("整数除法: " + a + "/" + b + " = " + (a / b)); // 3
System.out.println("浮点除法: " + a + "/" + b + " = " + ((double) a / b)); // 3.5
System.out.println("取模运算: " + a + "%" + b + " = " + (a % b)); // 1
// 分页计算(实际开发常用)
int totalItems = 23;
int pageSize = 5;
int totalPages = (totalItems + pageSize - 1) / pageSize; // 向上取整技巧
System.out.println("\n===== 分页计算 =====");
System.out.println("总条数: " + totalItems + ", 每页: " + pageSize + ", 总页数: " + totalPages);
// 前置++和后置++对比
System.out.println("\n===== ++前置 vs 后置 =====");
int x = 10;
int r1 = x++; // r1=10, x=11
int r2 = ++x; // x=12, r2=12
int r3 = x++ + ++x; // x++(12,x=13) + ++x(14,x=14) = 26
System.out.println("x=" + x + ", r1=" + r1 + ", r2=" + r2 + ", r3=" + r3);
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 3.2.6 算术运算符训练题
训练1:计算 (int)(10.0 / 3) 和 10 / 3 的结果分别是什么?它们的计算过程有什么区别?
训练2:以下代码的输出是什么?请逐步分析:
int x = 5;
int result = x++ + ++x + x-- + --x;
System.out.println(result); // ?
System.out.println(x); // ?
2
3
4
训练3:在循环中拼接字符串,分别用 + 和 StringBuilder 两种方式,对比 10000 次循环的耗时差异。
思考:为什么 Java 不像 C++ 那样支持运算符重载?运算符重载有什么潜在的问题?
# 3.3 赋值运算符
# 3.3.1 赋值案例
int a = 10;
a += 2; // a = a + 2 = 12
a -= 3; // a = a - 3 = 9
a *= 2; // a = a * 2 = 18
a /= 3; // a = a / 3 = 6
a %= 4; // a = a % 4 = 2
System.out.println(a); // 2
2
3
4
5
6
7
# 3.3.2 复合赋值隐含转换
复合赋值运算符会自动进行强制类型转换:
byte b = 10;
// b = b + 1; // 编译错误!b + 1 结果是 int
b += 1; // OK!等价于 b = (byte)(b + 1)
short s = 100;
// s = s + 1; // 编译错误!
s += 1; // OK!自动强制转换
2
3
4
5
6
7
疑惑:为什么 b = b + 1 编译报错,而 b += 1 却可以?
答疑:Java 的运算规则规定,byte、short 参与算术运算时会先自动提升为 int。所以 b + 1 的结果是 int 类型,赋给 byte 变量需要强制转换。但 b += 1 是复合赋值运算符,Java 语言规范(JLS §15.26.2)规定它隐含了强制类型转换,等价于 b = (byte)(b + 1)。
论证:这个设计是语言的便利性考虑。如果 += 不隐含转换,那每次对 byte/short 变量做运算都需要手动转换,代码会非常繁琐。但这也可能隐藏溢出问题:
byte b = 127;
b += 1; // 不报错!但 b = -128(溢出),等价于 b = (byte)(127 + 1)
2
# 3.3.3 赋值运算符训练题
训练1:以下代码能编译通过吗?如果能,结果是什么?
short s = 1;
s = s + 1; // ①
s += 1; // ②
s = (short)(s + 1); // ③
2
3
4
思考:a += b 和 a = a + b 在所有情况下都等价吗?什么时候会有区别?
# 3.4 比较运算符
# 3.4.1 比较案例
比较运算符的结果是 boolean 类型:
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); // true
System.out.println(a >= b); // false
System.out.println(a <= b); // true
2
3
4
5
6
7
# 3.4.2 equals和==区别
这是 Java 中非常重要的知识点:
// 基本类型:== 比较值
int a = 10, b = 10;
System.out.println(a == b); // true
// 引用类型:== 比较地址,equals 比较内容
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同对象,地址不同)
System.out.println(s1.equals(s2)); // true(内容相同)
// 字符串常量池的特殊情况
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // true(常量池中同一个对象)
2
3
4
5
6
7
8
9
10
11
12
13
14
对比 C++:C++ 中 == 可以被重载(如 std::string 重载了 == 比较内容),Java 中 == 对于对象始终比较地址,比较内容必须用 equals。
Integer 缓存池的陷阱:
Integer a = 127, b = 127;
System.out.println(a == b); // true(缓存池中的同一个对象)
Integer c = 128, d = 128;
System.out.println(c == d); // false(超出缓存范围,是不同对象)
System.out.println(c.equals(d)); // true(内容相同)
2
3
4
5
6
Integer 在 -128 到 127 范围内有缓存(IntegerCache),自动装箱时会复用缓存中的对象。超出范围则每次创建新对象。这是面试中的经典考点。
# 3.4.3 比较运算符训练题
训练1:以下代码分别输出什么?请解释原因:
String s1 = "hello";
String s2 = "hel" + "lo";
String s3 = new String("hello");
String s4 = s3.intern();
System.out.println(s1 == s2); // ?
System.out.println(s1 == s3); // ?
System.out.println(s1 == s4); // ?
2
3
4
5
6
7
8
训练2:编写一个方法,正确比较两个可能为 null 的字符串是否相等(避免 NullPointerException)。
思考:为什么 Java 建议使用 "literal".equals(variable) 而不是 variable.equals("literal")?
# 3.5 逻辑运算符
# 3.5.1 逻辑非
boolean a = true;
System.out.println(!a); // false
System.out.println(!!a); // true
2
3
# 3.5.2 逻辑与
System.out.println(true && true); // true
System.out.println(true && false); // false
System.out.println(false && false); // false
2
3
# 3.5.3 逻辑或
System.out.println(true || false); // true
System.out.println(false || false); // false
2
# 3.5.4 短路特性
&& 和 || 具有短路特性:
int a = 10;
// && 短路:第一个为 false,不会执行第二个
if (false && (++a > 0)) {}
System.out.println(a); // 10(++a 没有执行)
// || 短路:第一个为 true,不会执行第二个
if (true || (++a > 0)) {}
System.out.println(a); // 10(++a 没有执行)
2
3
4
5
6
7
8
Java 也有非短路的 & 和 |(用于逻辑运算时不短路),但实际开发中很少使用。
# 3.5.5 综合案例:用户登录验证器
编写一个程序,综合运用逻辑与、逻辑或、逻辑非和短路特性,实现一个用户登录验证系统。
import java.util.Scanner;
public class LoginValidator {
// 模拟数据库中的用户信息
static final String VALID_USERNAME = "admin";
static final String VALID_PASSWORD = "123456";
static final int MAX_ATTEMPTS = 3;
static final boolean SYSTEM_MAINTENANCE = false;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int attempts = 0;
boolean loggedIn = false;
System.out.println("===== 用户登录系统 =====");
// 短路或: 系统维护时直接拒绝
if (SYSTEM_MAINTENANCE || false) { // 短路特性: 第一个为true则不检查第二个
System.out.println("系统维护中,请稍后再试");
return;
}
while (!loggedIn && attempts < MAX_ATTEMPTS) {
System.out.print("用户名: ");
String username = sc.nextLine();
System.out.print("密码: ");
String password = sc.nextLine();
attempts++;
// 短路与:先判断非空,再判断内容(避免NPE)
boolean usernameValid = username != null && !username.isEmpty() && username.equals(VALID_USERNAME);
boolean passwordValid = password != null && !password.isEmpty() && password.equals(VALID_PASSWORD);
if (usernameValid && passwordValid) {
loggedIn = true;
System.out.println("登录成功! 欢迎 " + username);
} else {
int remaining = MAX_ATTEMPTS - attempts;
// 逻辑非
System.out.println("登录失败! " + (!usernameValid ? "用户名错误" : "密码错误"));
// 逻辑运算组合
if (remaining > 0) {
System.out.println("剩余尝试次数: " + remaining);
} else {
System.out.println("账号已锁定!");
}
}
System.out.println();
}
// 短路特性安全检查演示
System.out.println("===== 短路安全检查演示 =====");
String nullStr = null;
// 短路与: nullStr为null时不会调用length(),避免NPE
boolean safe = nullStr != null && nullStr.length() > 5;
System.out.println("null安全检查结果: " + safe); // false,不会抛NPE
// 非短路 & 会导致NPE
try {
boolean unsafe = nullStr != null & nullStr.length() > 5; // 两边都执行!
} catch (NullPointerException e) {
System.out.println("非短路&导致NullPointerException!");
}
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 3.5.6 逻辑运算符训练题
训练1:利用短路特性编写一段安全的空指针检查代码:
// 补全代码,使其不会抛出 NullPointerException
String str = null;
if (/* 你的条件 */) {
System.out.println(str.length());
}
2
3
4
5
训练2:以下代码中 a 和 b 最终的值分别是什么?
int a = 0, b = 0;
boolean r1 = (++a > 0) || (++b > 0);
System.out.println("a=" + a + ", b=" + b); // ?
boolean r2 = (a > 10) && (++b > 0);
System.out.println("a=" + a + ", b=" + b); // ?
2
3
4
5
思考:短路求值在哪些实际开发场景中特别有用?举出至少两个例子。
# 3.6 位运算符
int a = 5, b = 3; // 5: 0101, 3: 0011
System.out.println(a & b); // 1 (0001) 按位与
System.out.println(a | b); // 7 (0111) 按位或
System.out.println(a ^ b); // 6 (0110) 按位异或
System.out.println(~a); // -6 按位取反
System.out.println(a << 1); // 10 (1010) 左移
System.out.println(a >> 1); // 2 (0010) 有符号右移
System.out.println(a >>> 1); // 2 (0010) 无符号右移
2
3
4
5
6
7
8
对比 C++:Java 多了一个 >>> 无符号右移运算符。C++ 中右移运算的符号填充取决于实现,Java 明确区分了 >>(符号填充)和 >>>(零填充)。
位运算的实际应用场景:
// 1. 快速判断奇偶(比 % 2 更高效)
boolean isOdd = (num & 1) == 1;
// 2. 交换两个变量(不使用第三个变量)
a = a ^ b;
b = a ^ b; // b = (a^b)^b = a
a = a ^ b; // a = (a^b)^a = b
// 3. 权限管理(Linux 文件权限的原理)
int READ = 1; // 001
int WRITE = 2; // 010
int EXECUTE = 4; // 100
int permission = READ | WRITE; // 011 = 3,具有读写权限
boolean canRead = (permission & READ) != 0; // true
boolean canExec = (permission & EXECUTE) != 0; // false
permission |= EXECUTE; // 添加执行权限 → 111 = 7
permission &= ~WRITE; // 移除写权限 → 101 = 5
// 4. HashMap 计算桶索引(经典应用)
// index = hash & (capacity - 1) 当 capacity 是 2 的幂时等价于 hash % capacity
int index = "hello".hashCode() & (16 - 1); // 等价于 hashCode % 16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.6.1 综合案例:位运算权限管理器
编写一个程序,使用位运算实现一个完整的权限管理系统,演示位与、位或、位异或和移位操作的实际应用。
public class BitPermission {
// 用位定义权限(每一位代表一种权限)
static final int NONE = 0; // 0000
static final int READ = 1; // 0001
static final int WRITE = 1 << 1; // 0010
static final int DELETE = 1 << 2; // 0100
static final int ADMIN = 1 << 3; // 1000
// 添加权限(位或)
static int addPermission(int current, int perm) {
return current | perm;
}
// 移除权限(位与 + 取反)
static int removePermission(int current, int perm) {
return current & ~perm;
}
// 检查权限(位与)
static boolean hasPermission(int current, int perm) {
return (current & perm) == perm;
}
// 切换权限(位异或)
static int togglePermission(int current, int perm) {
return current ^ perm;
}
// 打印权限状态
static void printPermissions(String name, int perm) {
System.out.printf(" %s [%s]: R=%b W=%b D=%b A=%b%n", name,
String.format("%4s", Integer.toBinaryString(perm)).replace(' ', '0'),
hasPermission(perm, READ),
hasPermission(perm, WRITE),
hasPermission(perm, DELETE),
hasPermission(perm, ADMIN));
}
public static void main(String[] args) {
System.out.println("===== 权限管理系统 =====");
// 创建不同角色的权限
int visitor = READ; // 只读
int editor = READ | WRITE; // 读写
int moderator = READ | WRITE | DELETE; // 读写删
int admin = READ | WRITE | DELETE | ADMIN; // 全部权限
printPermissions("游客", visitor);
printPermissions("编辑", editor);
printPermissions("版主", moderator);
printPermissions("管理员", admin);
// 权限操作演示
System.out.println("\n===== 权限操作 =====");
int user = READ;
System.out.print("初始: "); printPermissions("用户", user);
user = addPermission(user, WRITE);
System.out.print("加写: "); printPermissions("用户", user);
user = addPermission(user, DELETE);
System.out.print("加删: "); printPermissions("用户", user);
user = removePermission(user, DELETE);
System.out.print("去删: "); printPermissions("用户", user);
user = togglePermission(user, WRITE);
System.out.print("切写: "); printPermissions("用户", user); // WRITE被切掉
// 不用临时变量交换两个数
System.out.println("\n===== 异或交换 =====");
int a = 42, b = 99;
System.out.println("交换前: a=" + a + ", b=" + b);
a = a ^ b;
b = a ^ b; // b = (a^b)^b = a
a = a ^ b; // a = (a^b)^a = b
System.out.println("交换后: a=" + a + ", b=" + b);
// 2的幂判断
System.out.println("\n===== 2的幂判断 =====");
for (int n : new int[]{1, 2, 3, 4, 8, 15, 16, 64, 100}) {
boolean isPow2 = n > 0 && (n & (n - 1)) == 0;
System.out.printf(" %3d: %s (二进制: %s)%n", n, isPow2 ? "是2的幂" : "否", Integer.toBinaryString(n));
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# 3.6.2 位运算符训练题
训练1:用位运算实现一个方法 boolean isPowerOfTwo(int n),判断一个正整数是否是 2 的幂。(提示:2 的幂的二进制只有一个 1)
训练2:不使用临时变量,用异或运算交换两个整数 a 和 b 的值。解释每一步的原理。
训练3:实现一个简单的权限系统:定义 READ=1, WRITE=2, DELETE=4, ADMIN=8 四种权限,编写方法 addPermission、removePermission、hasPermission 使用位运算实现。
思考:为什么 HashMap 要求容量是 2 的幂?hash & (capacity - 1) 和 hash % capacity 有什么关系?
# 3.7 运算符优先级
从高到低:
()(括号)++、--、!、~(一元运算符)*、/、%+、-<<、>>、>>><、<=、>、>=、instanceof==、!=&、^、|&&、||? :(三元运算符)=、+=、-=等
建议:不确定优先级时使用括号明确运算顺序,提高可读性。
# 3.7.1 综合案例:优先级陷阱检测器
编写一个程序,列举常见的运算符优先级陷阱,展示不加括号和加括号的不同结果。
public class PriorityTrap {
public static void main(String[] args) {
System.out.println("===== 运算符优先级陷阱 =====\n");
// 陷阱1:乘法优先于加法
int r1 = 2 + 3 * 4;
System.out.println("陷阱1: 2 + 3 * 4 = " + r1 + " (不是20,而是14)");
System.out.println(" 修正: (2 + 3) * 4 = " + ((2 + 3) * 4));
// 陷阱2:&& 优先于 ||
boolean r2 = true || false && false;
System.out.println("\n陷阱2: true || false && false = " + r2 + " (&&先算)");
System.out.println(" 等价于: true || (false && false) = true");
System.out.println(" 如果想先算||: (true || false) && false = " + ((true || false) && false));
// 陷阱3:位运算优先级低于比较运算
int x = 5;
// boolean r3 = x & 1 == 1; // 编译错误! 等价于 x & (1 == 1) → x & true → 类型错误
boolean r3 = (x & 1) == 1; // 正确写法
System.out.println("\n陷阱3: x & 1 == 1 编译错误!");
System.out.println(" 修正: (x & 1) == 1 = " + r3);
// 陷阱4:赋值优先级最低
int a = 1, b = 2;
// 意图是 a = b, 判断 a > 0; 实际是 a = (b > 0 的结果)? 不对,Java不允许
boolean r4 = (a = b) > 0; // 先赋值a=2,再比较2>0
System.out.println("\n陷阱4: (a = b) > 0 → a变为" + a + ", 结果=" + r4);
// 陷阱5:字符串拼接与算术混合
System.out.println("\n陷阱5: 字符串拼接顺序");
System.out.println(" 1 + 2 + \"abc\" = " + (1 + 2 + "abc")); // "3abc"
System.out.println(" \"abc\" + 1 + 2 = " + ("abc" + 1 + 2)); // "abc12"
System.out.println(" \"abc\" + (1 + 2) = " + ("abc" + (1 + 2))); // "abc3"
// 总结:优先级速记表
System.out.println("\n===== 优先级速记(从高到低) =====");
String[] levels = {
"1. () 括号",
"2. ++ -- ! ~ (一元)",
"3. * / %",
"4. + -",
"5. << >> >>>",
"6. < <= > >= instanceof",
"7. == !=",
"8. & ^ | (位运算)",
"9. && || (逻辑)",
"10. ?: (三元)",
"11. = += -= (赋值)"
};
for (String level : levels) {
System.out.println(" " + level);
}
System.out.println("\n建议: 不确定时加括号,可读性远比简洁重要!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 3.7.2 训练题
代码题:不运行代码,请预测以下表达式的值:
int a = 2 + 3 * 4; // ? boolean b = true || false && false; // ? int c = 5 & 3 | 2; // ?1
2
3改错题:以下代码的意图是判断 x 是否在 1~100 之间,但写法有误,请找出并修正:
int x = 50; if (1 < x < 100) { // 这行对吗? System.out.println("在范围内"); }1
2
3
4思考题:为什么 Java(和大多数编程语言)要定义运算符优先级?如果所有运算符优先级相同、严格从左到右计算,会有什么影响?
# 3.8 instanceof运算符
# 3.8.1 instanceof介绍
instanceof 是 Java 特有的运算符,用于判断一个对象是否是某个类(或接口)的实例。
# 3.8.2 基本用法
String str = "Hello";
System.out.println(str instanceof String); // true
System.out.println(str instanceof Object); // true
Object obj = new Integer(10);
System.out.println(obj instanceof Integer); // true
System.out.println(obj instanceof String); // false
// null 不是任何类的实例
System.out.println(null instanceof String); // false
2
3
4
5
6
7
8
9
10
# 3.8.3 模式匹配(JDK16+)
JDK 16 引入了 instanceof 模式匹配,可以在判断的同时完成类型转换:
Object obj = "Hello World";
// 传统写法
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
// JDK 16+ 模式匹配写法
if (obj instanceof String str) {
System.out.println(str.length()); // 直接使用 str
}
2
3
4
5
6
7
8
9
10
11
12
对比 C++:C++ 使用 dynamic_cast 进行运行时类型转换,失败返回 nullptr。Java 的 instanceof 更加简洁安全。
# 3.8.4 综合案例:多态类型识别器
编写一个程序,使用 instanceof 对不同类型的对象进行分类处理,并演示模式匹配的优势。
import java.util.ArrayList;
import java.util.List;
public class TypeIdentifier {
// 定义一个简单的图形类层次
static abstract class Shape {
abstract double area();
}
static class Circle extends Shape {
double radius;
Circle(double r) { this.radius = r; }
double area() { return Math.PI * radius * radius; }
}
static class Rectangle extends Shape {
double width, height;
Rectangle(double w, double h) { this.width = w; this.height = h; }
double area() { return width * height; }
}
static class Triangle extends Shape {
double base, height;
Triangle(double b, double h) { this.base = b; this.height = h; }
double area() { return 0.5 * base * height; }
}
// 传统写法:instanceof + 强制转换
static String describeTraditional(Object obj) {
if (obj instanceof Circle) {
Circle c = (Circle) obj;
return "圆形: 半径=" + c.radius + ", 面积=" + String.format("%.2f", c.area());
} else if (obj instanceof Rectangle) {
Rectangle r = (Rectangle) obj;
return "矩形: " + r.width + "×" + r.height + ", 面积=" + String.format("%.2f", r.area());
} else if (obj instanceof Triangle) {
Triangle t = (Triangle) obj;
return "三角形: 底=" + t.base + " 高=" + t.height + ", 面积=" + String.format("%.2f", t.area());
} else if (obj instanceof String) {
return "字符串: \"" + obj + "\"";
} else if (obj == null) {
return "null对象";
}
return "未知类型: " + obj.getClass().getSimpleName();
}
// JDK16+ 模式匹配写法(更简洁)
static String describeModern(Object obj) {
if (obj instanceof Circle c) {
return "圆形: 半径=" + c.radius + ", 面积=" + String.format("%.2f", c.area());
} else if (obj instanceof Rectangle r) {
return "矩形: " + r.width + "×" + r.height + ", 面积=" + String.format("%.2f", r.area());
} else if (obj instanceof Triangle t) {
return "三角形: 底=" + t.base + " 高=" + t.height + ", 面积=" + String.format("%.2f", t.area());
} else if (obj instanceof String s) {
return "字符串: \"" + s + "\", 长度=" + s.length();
} else if (obj == null) {
return "null对象";
}
return "未知类型: " + obj.getClass().getSimpleName();
}
public static void main(String[] args) {
List<Object> items = new ArrayList<>();
items.add(new Circle(5));
items.add(new Rectangle(4, 6));
items.add(new Triangle(3, 8));
items.add("Hello Java");
items.add(42);
items.add(null);
System.out.println("===== 类型识别(传统写法) =====");
for (Object item : items) {
System.out.println(" " + describeTraditional(item));
}
System.out.println("\n===== 类型识别(模式匹配JDK16+) =====");
for (Object item : items) {
System.out.println(" " + describeModern(item));
}
// instanceof 与继承关系
System.out.println("\n===== instanceof与继承 =====");
Shape shape = new Circle(3);
System.out.println("Circle instanceof Shape: " + (shape instanceof Shape)); // true
System.out.println("Circle instanceof Circle: " + (shape instanceof Circle)); // true
System.out.println("Circle instanceof Object: " + (shape instanceof Object)); // true
System.out.println("Circle instanceof Rectangle: " + (shape instanceof Rectangle)); // false
// null 判断
System.out.println("\n===== null判断 =====");
System.out.println("null instanceof Object: " + (null instanceof Object)); // false
System.out.println("null instanceof String: " + (null instanceof String)); // false
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 3.8.5 instanceof训练题
训练1:编写方法 String describeType(Object obj),使用 instanceof 模式匹配(JDK16+)判断参数类型(String、Integer、Double、List、null),返回类型描述和相关信息。
训练2:以下代码能编译通过吗?输出什么?
Integer num = null;
System.out.println(num instanceof Integer); // ?
System.out.println(num instanceof Object); // ?
2
3
思考:instanceof 在多态场景下,检查的是编译类型还是运行时类型?以下代码输出什么?
Object obj = "Hello";
System.out.println(obj instanceof String); // ?
System.out.println(obj instanceof Object); // ?
2
3
# 3.9 三元运算符
int a = 10, b = 20;
int max = (a > b) ? a : b;
System.out.println("较大值:" + max); // 20
// 可以嵌套,但不建议过度嵌套
String level = (score >= 90) ? "优秀" : (score >= 60) ? "及格" : "不及格";
2
3
4
5
6
三元运算符的类型推断规则:
// 三元运算符的两个分支必须类型兼容
int a = 10;
double b = 3.14;
// 当两个分支类型不同时,会自动类型提升
double result = true ? a : b; // int 提升为 double
System.out.println(result); // 10.0
// 一个经典陷阱
Object obj = true ? new Integer(1) : new Double(2.0);
System.out.println(obj); // 1.0(不是1!因为 Integer 被提升为 Double)
// 原因:编译器将两个分支统一为 double 类型
// 另一个陷阱:自动拆箱可能导致 NPE
Integer x = null;
int y = true ? x : 0; // NullPointerException!x 拆箱时为 null
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.9.1 综合案例:成绩等级评定器
编写一个程序,使用三元运算符实现成绩等级评定,并演示嵌套三元运算和类型推断陷阱。
import java.util.Scanner;
public class GradeEvaluator {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入成绩(0-100):");
int score = sc.nextInt();
// 基础三元运算
String passOrFail = score >= 60 ? "及格" : "不及格";
System.out.println("结果: " + passOrFail);
// 嵌套三元运算(不建议超过3层)
String grade = score >= 90 ? "A(优秀)"
: score >= 80 ? "B(良好)"
: score >= 70 ? "C(中等)"
: score >= 60 ? "D(及格)"
: "F(不及格)";
System.out.println("等级: " + grade);
// 三元运算求最大值
int a = 15, b = 28, c = 12;
int max = (a > b) ? (a > c ? a : c) : (b > c ? b : c);
System.out.println("\n三个数(" + a + "," + b + "," + c + ")的最大值: " + max);
// 三元运算求绝对值
int num = -42;
int abs = num >= 0 ? num : -num;
System.out.println(num + " 的绝对值: " + abs);
// 类型推断陷阱
System.out.println("\n===== 三元运算陷阱 =====");
// 陷阱1: 类型提升
int intVal = 1;
double doubleVal = 2.0;
// 两个分支类型不同时,int会提升为double
Object result1 = true ? intVal : doubleVal;
System.out.println("true ? int : double = " + result1 + " (类型: " + result1.getClass().getSimpleName() + ")");
// 陷阱2: 包装类自动拆箱 → NPE
System.out.println("\n陷阱2: null自动拆箱");
Integer nullInt = null;
try {
int danger = true ? nullInt : 0; // nullInt拆箱时NPE!
} catch (NullPointerException e) {
System.out.println(" NullPointerException! null的Integer拆箱失败");
}
// 安全写法
int safe = true ? (nullInt != null ? nullInt : 0) : 0;
System.out.println(" 安全写法结果: " + safe);
// 陷阱3: char与int的混合
char ch = 'X';
System.out.println("\ntrue ? 'X' : 0 = " + (true ? ch : 0)); // 88 (int)
System.out.println("true ? 'X' : \"\" = " + (true ? ch : "")); // X (String)
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 3.9.2 训练题
代码题:用三元运算符实现一个方法
String getSign(int num),根据数值返回 "正数"、"负数" 或 "零"。改错题:以下代码有什么问题?
Object result = true ? new Integer(1) : new Double(2.0); System.out.println(result); // 期望输出 1,实际输出什么?1
2思考题:三元运算符
? :可以嵌套使用,但过度嵌套会降低可读性。在实际项目中,嵌套超过几层时应改用 if-else?为什么?