一、语言基础与运算符(J01–J10)
J01 · 知识点:基本数据类型与默认值
【题目】以下关于 Java 基本数据类型的说法,正确的是? A. float 类型的默认值是 null,使用前必须显式赋值 B. boolean 类型的默认值是 true C. int 类型的默认值是 0 D. char 类型的默认值是空格字符 ' '
答案:C
【考点】基本数据类型的默认值,以及「实例变量 vs 局部变量」初始化规则的差异。
【结论】选 C。Java 成员变量由 JVM 自动赋予默认值,int 的默认值为 0。
【逐项辨析】
- B错:boolean 默认值为 false,不是 true。
- C正确:int 默认值为 0。
- A错:float 是基本类型,其默认值不是 null 而是 0.0f(JDK 17 实测输出 0.0,且
f == 0为 true);只有包装类型Float的成员变量默认值才是 null,A 混淆了基本类型与包装类型。 - D错:char 默认值为 '\u0000'(空字符,ASCII 码 0),不是空格字符 ' '(ASCII 码 32)。
【知识点】 Java 中成员变量(实例变量和静态变量)在对象创建或类加载时由 JVM 自动初始化。各基本类型的默认值如下:
| 类型 | 默认值 |
|---|---|
| boolean | false |
| byte / short / int / long | 0 / 0L |
| float / double | 0.0f / 0.0d |
| char | '\u0000'(空字符) |
| 引用类型 | null |
关键区分:局部变量没有默认值,使用前必须显式初始化,否则编译报错(如 int x; System.out.println(x); 直接报错)。
【记忆锚点】 「整零浮零点零,布尔 false 字符空」——整数默认 0,浮点默认 0.0,布尔 false,字符空('\u0000')。
【易混对比】
- 易混点:char 默认不是空格 ' ',而是空字符 '\u0000'(ASCII 码 0)。
- 进阶考法:问「局部变量的默认值是什么?」——答案是「没有默认值,必须显式初始化」。
- 与 C/C++ 区别:C/C++ 中局部变量有垃圾值,Java 中局部变量不初始化则编译失败。
【自测】 以下代码能否编译通过?
public class Test {
int a;
public void method() {
int b;
System.out.println(a + b);
}
}答:不能编译通过。
a是成员变量,默认值为 0,合法;b是局部变量,没有默认值,直接参与运算会导致编译错误。与 J01 同类考点。
【知识关联】
- 同库关联:与 J10(数组声明)、J20(初始化顺序)同属「初始化与默认值」题群;与 J19(static 成员初始化时机)对照记忆。
- 实现层:JVM 在对象分配后、构造器执行前,由字节码
new+dup+invokespecial完成内存清零(默认值);局部变量则在编译期由 javac 做「可能未初始化」数据流分析,不满足则直接编译失败,不进入字节码。 - 面试追问:① 成员变量有默认值,为什么局部变量没有?②
static final int x;不赋值能否编译通过?
【拓展延伸】
- 变式问法:给出一段含成员/局部变量的代码,问能否编译;或问「引用类型成员变量的默认值」(null)。
- 版本差异:JDK 1.0 起即有此规则,各 LTS(8/11/17/21)无变化;与 C#/Go 的默认值策略不同(Go 零值覆盖所有变量,C# 字段有零值、局部无)。
- 工程注意点:依赖字段默认值「隐式置 0」容易掩盖未初始化 bug;阿里 Java 规约建议显式初始化或在构造器中赋值;JSON 反序列化时缺字段也会落到默认值,注意与「字段不存在」区分。
J02 · 知识点:自增自减运算符
【题目】执行下列代码后,x 的值是?
int a = 5;
int x = a++ + ++a;A. 10 B. 11 C. 12 D. 13
答案:C
【考点】前置自增(++i)与后置自增(i++)的取值时机。
【结论】选 C。x = a++ + ++a = 5 + 7 = 12。
【推导过程】
a++是后置自增:先取当前值 5 参与运算,然后 a 自增为 6。- 此时 a = 6,遇到
++a(前置自增):先把 a 自增为 7,再取 7 参与运算。 - x = 5 + 7 = 12。
【逐项辨析】
- A 10 错:若误把 a++ 和 ++a 都当作 5,则 5+5=10。
- B 11 错:若把 a++ 当作 5、++a 当作 6(忽略了 a++ 已把 a 改成 6),则 5+6=11。
- C 12 正确:按实际执行顺序 5 + 7 = 12。
- D 13 错:无对应计算路径。
【知识点】
前置自增(++i)与后置自增(i++)的核心区别:前置先自增再使用新值;后置先使用旧值再自增。同一表达式中,自增会立即写回变量本身,后续操作数读到的是更新后的值。这是笔试最经典的「坑题」之一。
执行模型(以 int a=5; int x=a++ + ++a; 为例):从左到右求值——a++ 先把 5 压入操作数栈,再把 a 写成 6;++a 先把 a 写成 7,再把 7 压栈;最后 iadd 得 12。字节码层面是 iload/iinc 的交错,不是「整句算完再自增」。
【记忆锚点】 「前置先增后用,后置先用后增」——同时注意:变量在表达式中被修改后,后续读取的是新值。
【易混对比】
- 易混:
a++ + ++a与++a + a++结果不同。若int a=5; int x=++a + a++;,则 ++a 先使 a=6 并取 6,a++ 取 6 再使 a=7,x = 12(但 a 最终为 7)。 - 类似陷阱:
System.out.println(a++ + a++ + a++)从左到右依次取 5、6、7,输出 18。
【自测】int a = 5; int x = ++a + a++ + ++a; 执行后 x 和 a 分别是多少?
答:x = 20,a = 8。推导:++a 使 a=6 取 6;a++ 取 6 使 a=7;++a 使 a=8 取 8;x = 6 + 6 + 8 = 20。与 J02 同类考点。
【知识关联】
- 同库关联:与 J05/J73(移位)、J06(三元类型提升)、J07(字符串拼接)、J08(短路求值)同属「表达式求值顺序」题群;与 J70(JMM/happens-before)衔接——多线程下 a++ 不是原子操作。
- 实现层:javac 把
i++编译为先 iload 再 iinc;++i是先 iinc 再 iload。JIT 后仍保持「读旧值/写新值」语义,但 a++ 在并发下会有丢失更新。 - 面试追问:①
a++为何不是线程安全的?② 为什么《阿里巴巴 Java 手册》禁止在复杂表达式中使用++/--?
【拓展延伸】
- 变式问法:
i = i++(经典陷阱,结果仍为旧值);int i=0; i = i++ + ++i;;或给System.out.println(i++ + i++ + ++i)逐步推导。 - 版本差异:JDK 各版本语义一致;Kotlin 中
++仅作为独立语句更安全,表达式嵌套不推荐。 - 工程注意点:可读性优先用
i += 1;循环变量用增强 for 或IntStream;并发计数改用AtomicInteger.incrementAndGet()(对应 J60 CAS)。
J03 · 知识点:整数除法与类型转换
【题目】int a = 7; double b = a / 2; 执行后 b 的值是? A. 2.5 B. 3.5 C. 3.0 D. 编译错误
答案:C
【考点】整数除法截断规则与运算时的类型提升(不是赋值时才转换)。
【结论】选 C。b 的值为 3.0。
【推导过程】
a / 2中,a 和 2 都是 int 类型,Java 执行整数除法:7 / 2 = 3(小数部分直接截断)。- 得到 int 类型的 3,赋值给 double 类型的 b,发生隐式拓宽转换:3 → 3.0。
- 因此 b 的值为 3.0。
【逐项辨析】
- C 3.0 正确:整数除法得 3,再自动转 double 得 3.0。
- B 3.5 错:3.5 只有在运算前将其中一个操作数提升为浮点(如
a / 2.0)才会得到。 - A 2.5 错:无对应计算路径。
- D 编译错误 错:int 赋值给 double 是合法的隐式拓宽转换。
【知识点】 Java 的算术运算遵循二进制数值提升规则:若两个操作数都是整数类型,则执行整数除法,结果截断小数部分。赋值时的隐式转换不会恢复运算中已丢失的小数。
正确获得 3.5 的写法:
a / 2.0(2.0 是 double,触发浮点除法)(double) a / 2(强制类型提升)a * 1.0 / 2
【记忆锚点】 「整数相除截小数,赋值转换不找回」——想保留小数,必须在运算前提升类型。
【易混对比】
- 易混:
7 / 2在 Java/C/C++ 中结果为 3(截断),但在 Python 3 中7 / 2= 3.5(真除法),7 // 2= 3(地板除)。 - 进阶考法:
double b = (double)(a / 2);结果仍是 3.0——因为括号先算整数除法,再强制转换。
【自测】int a = 7; double b = (double) a / 2; double c = a / (double) 2; double d = (double) (a / 2); 中 b、c、d 分别是多少?
答:b = 3.5,c = 3.5,d = 3.0。前两者在除法前已提升为浮点;d 是先做整数除法得 3,再转 double。与 J03 同类考点。
【知识关联】
- 同库关联:与 J06(三元运算符类型提升)、J74(强制类型转换向零取整)同属「数值类型转换」题群;与 P03(Python 除法)形成跨语言对照——Python3
/为真除法。 - 实现层:JVM 指令
idiv做整数除法并丢弃余数;i2d再拓宽。IEEE 754 的ddiv才产生小数。Python3 对应 ceval 中BINARY_TRUE_DIVIDE/BINARY_FLOOR_DIVIDE。 - 面试追问:①
(int)(-3.99)与Math.round(-3.5)各是多少?② 如何避免金额计算中的整数截断?
【拓展延伸】
- 变式问法:比较
7/2、7/2.0、(double)7/2、(double)(7/2);或问5/2*2.0的结果。 - 版本差异:Java 语义稳定;Python 2 的
/对整数是地板除,Python 3 改为真除法(历史大坑)。 - 工程注意点:金额用
BigDecimal而不是 double;百分比计算先转 double 再除;整数平均用(a+b)/2.0或无溢出写法(a&b)+((a^b)>>1)。
J04 · 知识点:switch 语句
【题目】以下关于 switch 语句的说法,正确的是?
A. switch 的判断条件可以是 long 类型 B. 每个 case 分支后不写 break 不会产生任何影响 C. case 后的值可以是变量 D. switch 的判断条件可以是 String 类型(JDK7+)
答案:D
【考点】switch 支持的数据类型演进、case 标签必须是编译期常量、case 穿透。
【结论】选 D。JDK7 起 switch 支持 String 类型。
【逐项辨析】
- A错:switch 不支持 long、float、double、boolean。
- D正确:JDK7 起支持 String,此前仅支持 byte、short、char、int 及其包装类、枚举。
- C错:case 标签必须是编译期常量(字面量或 final 常量),不能是变量。
- B错:不写 break 会导致 case 穿透(fall-through),从命中 case 开始依次执行后续分支。
【知识点】 switch 支持类型的版本演进:
- JDK 1.0:byte、short、char、int
- JDK 5:枚举(enum)
- JDK 7:String
- 始终不支持:long、float、double、boolean
case 穿透机制:若某个 case 分支没有 break,程序会继续执行下一个 case 的代码,直到遇到 break 或 switch 结束。这是常见的设计模式(多个 case 共享逻辑),也是常见的笔试陷阱。
【记忆锚点】 「switch 只认整枚串,长浮布不进来」——整型、枚举、String 可以;long、float、double、boolean 不行。
【易混对比】
- 易混:JDK7 支持 String 是通过编译期将 String 的 hashCode 与 equals 转化为底层整型 switch 实现的,并非 JVM 原生支持 String switch。
- 进阶考法:问「case 标签可以用变量吗?」——不可以,必须是编译期常量。
【自测】 以下代码输出什么?
int x = 2;
switch (x) {
case 1: System.out.print("A");
case 2: System.out.print("B");
case 3: System.out.print("C");
default: System.out.print("D");
}答:BCD。case 2 命中后没有 break,发生 case 穿透,依次执行 case 3 和 default。与 J04 同类考点。
【知识关联】
- 同库关联:与 J72(枚举可在 switch 中使用)直接连考;与 J08(控制流)同属流程控制题群。
- 实现层:String switch 由 javac 拆成
hashCodeswitch +equals校验;enum switch 编译为ordinal()整型 switch。JVM 指令为lookupswitch/tableswitch。 - 面试追问:① 为何 switch 不能用 long?② JDK 14+ 的 switch 表达式(yield)如何消除 fall-through?
【拓展延伸】
- 变式问法:给出穿透代码问输出;问 case 能否用变量/枚举常量名;问
switch(null)行为(NPE 或 String null 特判)。 - 版本差异:JDK 7 支持 String;JDK 14 正式引入 switch 表达式与
->箭头语法(无穿透);JDK 17/21 该语法为标准特性。 - 工程注意点:新代码优先
->避免忘写 break;多个 case 共享逻辑用 fall-through 要加注释;枚举状态机是 switch 的最佳搭档(J72)。
J05 · 知识点:移位运算
【题目】设 x = 4,下列表达式中结果与其他三项不同的是? A. x << 1 B. x * 2 C. x >> 1 D. x + x
答案:C
【考点】移位运算的数学含义:左移 n 位 ≈ 乘以 2^n,右移 n 位 ≈ 除以 2^n。
【结论】选 C。x >> 1 = 2,其余三项结果均为 8。
【逐项辨析】
- A x << 1 错:左移 1 位 = 4 × 2 = 8。
- C x >> 1 正确:右移 1 位 = 4 / 2 = 2。
- B
x * 2错:结果为 8。 - D x + x 错:结果为 8。
【知识点】 移位运算的数学含义与规则:
- 左移 n 位(<<):相当于乘以 2ⁿ。高位丢弃,低位补 0。
- 算术右移 n 位(>>):相当于除以 2ⁿ 后向下取整。高位补符号位(正数补 0,负数补 1)。
- 逻辑右移 n 位(>>>):高位始终补 0,无论正负。
| 运算符 | 作用 | 高位补什么 | 适用场景 |
|---|---|---|---|
| << | 乘以 2ⁿ | 0 | 快速乘 2 |
| >> | 除以 2ⁿ(向下取整) | 符号位 | 有符号数除 2 |
| >>> | 无符号右移 | 0 | 仅用于特定位操作 |
补充:int 移位只使用低 5 位作为移位位数(即 1 << 33 等价于 1 << 1)。
【记忆锚点】 「左移乘二右移除,算术补符逻辑补零」。
【易混对比】
- 易混:
>>对负数是向下取整(如 -3 >> 1 = -2),而/对负数在 Java 中也是向零取整(-3 / 2 = -1),两者对负数结果不同。 - 进阶考法:问
-8 >> 1和-8 >>> 1的区别——前者补 1 得 -4,后者补 0 得 2147483644。
【自测】int x = -8; System.out.println(x >> 1); System.out.println(x >>> 1); 输出分别是?
答:-4 和 2147483644。>> 补符号位 1,结果仍为负;>>> 补 0,结果为正。与 J05 同类考点。
【知识关联】
- 同库关联:与 J73(左移乘 2^n)几乎同一考点,建议连做;与 J70(JMM)无直接关系但并发位运算常用。
- 实现层:JVM
ishl/ishr/iushr;>>保留符号位,>>>高位补 0。HashMap 扰动函数h ^ (h >>> 16)就用无符号右移(见 J34)。 - 面试追问:①
-1 >>> 1是多少?② 为何 HashMap 要用>>>而不是>>?
【拓展延伸】
- 变式问法:比较
x<<1、x*2、x+1、x>>1;或问1<<31与Integer.MIN_VALUE。 - 版本差异:Java 各版本一致;Python 用
<<>>,无>>>(无符号)。 - 工程注意点:移位做乘除仅适用于 2 的幂且需防溢出;位图/权限掩码常用;
>>对负数是向下取整,与/向零取整不同(见易混对比)。
J06 · 知识点:三元运算符的类型提升
【题目】执行下列代码,输出结果是?
Object o = true ? new Integer(1) : new Double(2.0);
System.out.println(o);A. 1 B. 编译错误 C. 1.0 D. 2.0
答案:C
【考点】三元运算符的二进制数字类型提升规则。
【结论】选 C。输出 1.0。
【推导过程】
- 三元运算符
true ? new Integer(1) : new Double(2.0)中,两个分支类型分别为 Integer 和 Double。 - 编译器按二进制数字类型提升规则,将 Integer 提升为 double(因为 Double 的原始类型是 double)。
- 因此整个表达式的编译期类型为 double,运行时 Integer(1) 自动拆箱为 1,再提升为 1.0。
- 输出 1.0。
【逐项辨析】
- A 1 错:虽然条件为 true 选了 Integer 分支,但类型提升后结果是 double 类型的 1.0。
- C 1.0 正确:Integer 被提升为 double,输出 1.0。
- B 编译错误 错:三元运算符允许不同类型分支,会取公共类型。
- D 2.0 错:2.0 是 false 分支的值,但条件为 true,不会选它。
【知识点】 三元运算符的类型推导规则:当两个分支类型不同时,编译器取两者的最小公共父类型。若两者都是数字包装类(Integer、Double 等),则按二进制数值提升规则统一到更大的类型(int → long → float → double)。
核心陷阱:类型提升发生在编译期,由表达式整体类型决定,而不是由运行时选择的分支决定。因此即使 true 分支选了 Integer,结果类型仍是 double。
【记忆锚点】 「三元选分支,类型看整体——包装类遇包装类,提升到大类型」。
【易混对比】
- 易混:若写成
true ? 1 : 2.0,结果也是 1.0(int 提升为 double)。 - 进阶考法:
Object o = true ? new Integer(1) : new Double(2.0);的运行时类型是 Double 吗?——是。三元运算符两侧是 Integer 与 Double 时会做二进制数值提升,表达式类型变为 double(基本类型),再由Object o =自动装箱成 Double;实测o.getClass()=class java.lang.Double,值为 2.0。别把「表达式是基本类型 double」误说成「o 不是 Double」。
【自测】Object o = false ? 1 : 2.0; System.out.println(o.getClass()); 输出什么?
答:
class java.lang.Double。false 分支 2.0 使表达式类型为 double,自动装箱为 Double。即使选了 false 分支,o 的类型仍是 Double(不是 Integer)。与 J06 同类考点。
【知识关联】
- 同库关联:与 J03(整数除法)、J28/J29/J30(包装类拆箱)、J74(强制转换)同属类型提升题群。
- 实现层:三元运算符若两分支类型不同,编译期做二进制数值提升,字节码中会插入
i2d,使 Integer 被拆箱后拓宽为 double,再装箱成 Double。 - 面试追问:① 三元运算符何时会触发自动拆箱 NPE?②
true ? null : 1的类型是什么?
【拓展延伸】
- 变式问法:
Object o = true ? new Integer(1) : new Double(2.0);问输出;或int x = flag ? a : b当 a 为 Integer 可能为 null 时的 NPE。 - 版本差异:JDK 9+ 标记
new Integer废弃,应改用Integer.valueOf/ 自动装箱;JDK 17/21 中三元提升规则不变。 - 工程注意点:避免在三元两分支混用不同包装类型;用显式类型声明 Object 前先想清楚装箱类型;与 Optional 组合时注意拆箱。
J07 · 知识点:字符串与数字混合拼接
【题目】下列两行代码的输出分别是?
System.out.println("5" + 3 + 2);
System.out.println(5 + 3 + "2");A. 532 和 532 B. 55 和 82 C. 532 和 82 D. 82 和 532
答案:C
【考点】+ 运算符从左到右结合,字符串拼接与数字相加的优先级规则。
【结论】选 C。第一行输出 532,第二行输出 82。
【推导过程】
"5" + 3 + 2:从左到右计算。"5" + 3→ 字符串拼接得"53";"53" + 2→ 得"532"。5 + 3 + "2":从左到右计算。5 + 3→ 整数加法得 8;8 + "2"→ 字符串拼接得"82"。
【逐项辨析】
- C 532 和 82 正确:按上述推导。
- B 55 和 82 错:82 恰好是第二行的正确输出,但第一行的 55 来自把
"5" + 3 + 2误当成"5" + (3 + 2)——先算 3+2=5 再拼接得 "55"(实测输出即为 55),同样是漏掉了「从左到右」结合;两行合起来并不等于本题结果。 - A 532 和 532 错:第二行若误把
5 + 3 + "2"全当字符串拼接才会得 "532"。 - D 82 和 532 错:顺序完全颠倒。
【知识点】+ 运算符在 Java 中的双重语义:
- 两侧都是数字 → 算术加法。
- 任一側是字符串 → 字符串拼接,且结果类型为 String。
结合律是严格从左到右。一旦某一步出现了字符串,后续所有 + 都变为拼接操作。
| 表达式 | 第一步 | 第二步 | 结果 |
|---|---|---|---|
"5" + 3 + 2 | "5"+3="53" | "53"+2="532" | "532" |
5 + 3 + "2" | 5+3=8 | 8+"2"="82" | "82" |
【记忆锚点】 「从左到右步步算,一遇字符串全变拼」。
【易混对比】
- 易混:
5 + 3 + '2'中'2'是 char(Unicode 值 50),所以5 + 3 + '2' = 8 + 50 = 58,不是字符串拼接。 - 进阶考法:
System.out.println("" + 5 + 3 + 2)输出 532(空字符串触发拼接)。
【自测】System.out.println(5 + 3 + '2' + "1"); 输出什么?
答:
581。推导:5+3=8;8+'2'(=50)=58;58+"1"="581"。先算术运算,后字符串拼接。与 J07 同类考点。
【知识关联】
- 同库关联:与 J23–J26(String/StringBuilder)同属字符串题群;与 J25(编译期拼接优化)、J28(Integer 缓存)相关——拼接 Integer 会走 toString。
- 实现层:JDK 8 及以前
+编译为StringBuilder.append;JDK 9+ 改为invokedynamic+StringConcatFactory,可选择策略(MH_INLINE、MH_BOUND 等),性能与内存更优。 - 面试追问:① 循环内
s += i为何慢?② JDK 9 字符串拼接 indy 带来什么好处?
【拓展延伸】
- 变式问法:
"a"+1+2与1+2+"a"结果不同;""+1/2输出 0;或与 J03 联动问"result:" + 7/2。 - 版本差异:JDK 8 StringBuilder 链;JDK 9+ invokedynamic;Python 对应
+/ f-string /str.format(见 P08/P39)。 - 工程注意点:循环拼接用 StringBuilder(单线程)或 StringJoiner;日志用参数化日志而不是
+;避免在拼接中混用除法导致静默截断。
J08 · 知识点:逻辑运算符短路
【题目】执行下列代码,输出结果是?
int a = 3, b = 2;
if (a > 3 && b++ > 1) { }
System.out.println(b);A. 4 B. 3 C. 2 D. 编译错误
答案:C
【考点】&& 与 || 的短路求值(short-circuit),以及 &/| 的非短路区别。
【结论】选 C。b 的值为 2。
【推导过程】
a > 3中 a = 3,所以3 > 3为 false。&&具备短路特性:左侧为 false 时,右侧表达式不再执行。- 因此
b++ > 1不会执行,b 保持原值 2。 - 输出 b = 2。
【逐项辨析】
- C 2 正确:短路导致 b++ 未执行。
- B 3 错:若把
&&误当成&(非短路逻辑与),则 b++ 会执行,b 变为 3。 - A 4 错:无对应计算路径。
- D 编译错误 错:代码语法完全合法。
【知识点】 逻辑运算符的短路求值(short-circuit evaluation):
&&:左侧为 false 时,右侧不执行。||:左侧为 true 时,右侧不执行。&和|:非短路,两侧始终执行(同时还是位运算符)。
短路特性对副作用(自增、方法调用、赋值)有决定性影响,是笔试高频陷阱。
【记忆锚点】 「与见假就停,或见真就停,单与单或不停」。
【易混对比】
- 易混:
&和|在位运算中表示按位与/或,在布尔运算中表示非短路逻辑与/或。 - 进阶考法:
if (a++ > 3 && b++ > 1)与if (a++ > 3 & b++ > 1)中 a 和 b 的最终值不同——前者 a=4、b=2(短路),后者 a=4、b=3(非短路)。
【自测】int a = 3, b = 2; if (++a > 3 || b++ > 1) { } System.out.println(a + " " + b); 输出什么?
答:
4 2。++a > 3为 true,||短路,b++ 不执行。注意 ++a 先使 a=4,再判断 4>3。与 J08 同类考点。
【知识关联】
- 同库关联:与 J04(switch 控制流)、J45–J50(异常)相关——短路可避免除零/空指针;与 Python 的
and/or短路(P06 真值测试)对照。 - 实现层:javac 生成条件跳转指令(ifle 等),
&&右侧仅在左侧为 true 时才求值;&则两侧都求值且对 boolean 是非短路与。 - 面试追问:①
a() && b()与a() & b()的副作用差异?② 如何用短路安全写obj != null && obj.foo()?
【拓展延伸】
- 变式问法:给带副作用的
f() && g()问调用次数;或if (x != 0 && 10/x > 1)为何安全。 - 版本差异:语义稳定;Python 的
and/or返回操作数本身而非 bool(见 P06)。 - 工程注意点:校验链把「便宜且安全」的判断放前面;不要依赖短路去做资源清理(应 try-with-resources,J50);
&/|用在整型位运算,用在 boolean 时务必确认需要两侧求值。
J09 · 知识点:final 关键字
【题目】关于 final 关键字的说法,正确的是?
A. final 修饰的方法可以被重写 B. final 修饰的类可以被继承 C. final 修饰的引用变量不能再指向其他对象,但对象内部状态可以修改 D. final 修饰的基本类型变量可以在对象构造完成后再赋值
答案:C
【考点】final 的三种用法:修饰类、修饰方法、修饰变量,及其各自边界。
【结论】选 C。final 引用变量不能指向其他对象,但对象内部状态可以修改。
【逐项辨析】
- A 错:final 方法不可被重写(但可以被重载)。
- B 错:final 类不可被继承(如 String、Math)。
- C 正确:final 只锁定引用指向,不锁定对象内容。例如
final StringBuilder sb = new StringBuilder(); sb.append("x")完全合法。 - D 错:final 基本类型变量必须在声明时、静态块或构造器中初始化,构造完成后不能再赋值。
【知识点】 final 的三种用法与边界:
| 修饰目标 | 效果 | 示例 |
|---|---|---|
| 类 | 不可被继承 | final class String |
| 方法 | 不可被重写 | final void method() |
| 变量(基本类型) | 值不可变 | final int MAX = 100; |
| 变量(引用类型) | 引用不可变,对象内容可变 | final List list = new ArrayList(); list.add(1) 合法 |
关键认知:final ≠ 不可变。final 只保证引用地址不变,对象内部状态仍可修改。真正的“不可变对象”需要类本身设计为不可变(如 String)。
【记忆锚点】 「final 锁引用不锁内容,基本类型锁值,引用类型锁地址」。
【易混对比】
- 易混:final、finally、finalize 三者区别——final 是修饰符,finally 是异常处理块,finalize 是 Object 的析构方法(JDK9+ 已废弃)。
- 进阶考法:
final StringBuilder sb = new StringBuilder(); sb = new StringBuilder();编译报错——引用不能重新指向。
【自测】 以下代码能否编译通过?
final StringBuilder sb = new StringBuilder("A");
sb.append("B");
sb = new StringBuilder("C");答:第三行编译报错。
sb.append("B")合法(修改对象内容);sb = new StringBuilder("C")非法(修改引用指向)。与 J09 同类考点。
【知识关联】
- 同库关联:与 J20(初始化顺序)、J70(JMM final 语义)、J23(String 不可变依赖 final)关联;与 J19(static final)连考。
- 实现层:final 字段由 JVM 写后不再变;JMM 保证 final 字段在构造器结束后对其他线程可见(final 字段安全发布语义),这是 String 不可变且线程安全的基础。
- 面试追问:① final 引用能否修改对象内部状态?② 为何 String 要设计成不可变?
【拓展延伸】
- 变式问法:
final List list能否list.add;final int x在构造器中赋值一次的规则;抽象方法能否 final。 - 版本差异:JDK 5 后 JMM 明确 final 安全语义;JDK 14+ 可用 record(隐式 final 字段)。
- 工程注意点:优先不可变对象;集合用
List.of/Collections.unmodifiableList;避免 final 可变对象造成的「伪不可变」。
J10 · 知识点:数组声明与初始化
【题目】以下数组声明错误的是?
A. int[] a = new int[3]; B. int a[] = new int[]{1, 2, 3}; C. int[] a = new int[3]{1, 2, 3}; D. int[][] b = new int[3][];
答案:C
【考点】数组创建的两种方式(指定长度 / 指定元素列表)不可混用;不规则二维数组。
【结论】选 C。new int[3]{1, 2, 3} 同时指定长度和元素列表,语法非法。
【逐项辨析】
- A 正确:
new int[3]指定长度,元素默认为 0。 - B 正确:
new int[]{1, 2, 3}指定元素列表,编译器自动推断长度为 3。 - C 错误:不能同时写
[3]和{1,2,3},二者只能选其一。 - D 正确:
new int[3][]声明 3 行的不规则二维数组(每行列数未定)。
【知识点】 Java 数组创建的两种方式:
- 指定长度:
new int[3]—— 元素赋默认值。 - 指定元素:
new int[]{1, 2, 3}—— 长度由元素个数推断。
两种方式不可混用。不规则二维数组(jagged array)new int[3][] 是合法语法:先分配 3 个行引用(初始为 null),再分别为每行分配不同长度。
| 写法 | 合法性 | 说明 |
|---|---|---|
int[] a = new int[3]; | 合法 | 指定长度 |
int[] a = new int[]{1,2,3}; | 合法 | 指定元素 |
int[] a = {1,2,3}; | 合法 | 简写形式(仅声明时可用) |
int[] a = new int[3]{1,2,3}; | 非法 | 混用 |
【记忆锚点】 「数组创建二选一:要么给长度,要么给元素,不能两头都占」。
【易混对比】
- 易混:
int[] a = {1,2,3};是简写,只能在声明时用;a = {1,2,3};单独赋值时非法,必须写成a = new int[]{1,2,3}。 - 进阶考法:
int[][] arr = new int[3][3];与int[][] arr = new int[3][];的区别——前者是规则二维数组(3×3),后者是不规则二维数组(3 行,每行长度未定)。
【自测】 以下哪些数组声明是合法的?(多选) A. int[] a = new int[3]; B. int[] a = new int[]{1,2,3}; C. int[] a = new int[3]{1,2,3}; D. int[][] a = new int[3][];
答:A、B、D 合法,C 非法。与 J10 同类考点。
【知识关联】
- 同库关联:与 J01(默认值)、J20(初始化)、J31(集合体系)对照——定长数组 vs 可变 List;与 P11(Python 列表)跨语言比较。
- 实现层:
new int[3]在堆上分配,元素由 JVM 清零;数组是协变的(Object[] = new String[10]合法),运行期有 ArrayStoreException,与泛型不变性(J71)对比。 - 面试追问:① 数组协变与泛型不变谁更安全?②
int[]与Integer[]的默认值差异?
【拓展延伸】
- 变式问法:
int[] a = new int[3];a[0] 是多少;二维数组是否真「矩阵」;new int[-1]抛 NegativeArraySizeException。 - 版本差异:JDK 各版本一致;Python 用 list(P11),无原生定长数组(array 模块/numpy 另论)。
- 工程注意点:大数据量优先
ArrayList或专用集合;算法题常用Arrays.fill/Arrays.sort;注意数组越界是运行时异常。