Skip to content

一、语言基础与运算符(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 自动初始化。各基本类型的默认值如下:

类型默认值
booleanfalse
byte / short / int / long0 / 0L
float / double0.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 中局部变量不初始化则编译失败。

【自测】 以下代码能否编译通过?

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 的值是?

java
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。

【推导过程】

  1. a++ 是后置自增:先取当前值 5 参与运算,然后 a 自增为 6。
  2. 此时 a = 6,遇到 ++a(前置自增):先把 a 自增为 7,再取 7 参与运算。
  3. 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。

【推导过程】

  1. a / 2 中,a 和 2 都是 int 类型,Java 执行整数除法:7 / 2 = 3(小数部分直接截断)。
  2. 得到 int 类型的 3,赋值给 double 类型的 b,发生隐式拓宽转换:3 → 3.0。
  3. 因此 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 标签可以用变量吗?」——不可以,必须是编译期常量。

【自测】 以下代码输出什么?

java
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 拆成 hashCode switch + 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 · 知识点:三元运算符的类型提升 ​

【题目】执行下列代码,输出结果是?

java
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。

【推导过程】

  1. 三元运算符 true ? new Integer(1) : new Double(2.0) 中,两个分支类型分别为 Integer 和 Double。
  2. 编译器按二进制数字类型提升规则,将 Integer 提升为 double(因为 Double 的原始类型是 double)。
  3. 因此整个表达式的编译期类型为 double,运行时 Integer(1) 自动拆箱为 1,再提升为 1.0。
  4. 输出 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 · 知识点:字符串与数字混合拼接 ​

【题目】下列两行代码的输出分别是?

java
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。

【推导过程】

  1. "5" + 3 + 2:从左到右计算。"5" + 3 → 字符串拼接得 "53";"53" + 2 → 得 "532"。
  2. 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=88+"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 · 知识点:逻辑运算符短路 ​

【题目】执行下列代码,输出结果是?

java
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。

【推导过程】

  1. a > 3 中 a = 3,所以 3 > 3 为 false。
  2. && 具备短路特性:左侧为 false 时,右侧表达式不再执行。
  3. 因此 b++ > 1 不会执行,b 保持原值 2。
  4. 输出 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(); 编译报错——引用不能重新指向。

【自测】 以下代码能否编译通过?

java
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 数组创建的两种方式:

  1. 指定长度:new int[3] —— 元素赋默认值。
  2. 指定元素: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;注意数组越界是运行时异常。

持续学习,持续积累。