二、面向对象(J11–J22)
J11 · 知识点:重载(Overload)
【题目】以下哪种情况属于方法重载(Overload)? A. 子类中重写父类方法,方法签名相同 B. 子类中定义与父类同名同参但返回类型不同的方法 C. 同一个类中方法名相同、参数列表不同 D. 方法名不同、参数列表相同
答案:C
【考点】重载的判定标准:方法名相同 + 参数列表不同(个数/类型/顺序),与返回类型无关。
【结论】选 C。方法重载要求同一类中方法名相同、参数列表不同,返回类型不参与判定。
【逐项辨析】
- A错:子类重写父类方法是 Override(运行期多态),不是 Overload(编译期多态)。
- C正确:同一个类中方法名相同、参数个数 / 类型 / 顺序不同即构成重载。
- B错:同名同参仅返回类型不同,在同一类中既不构成重载也不构成重写,会直接编译错误。
- D错:方法名不同不构成重载,重载的核心就是“同名不同参”。
【知识点】 重载(Overload)是编译期多态(静态绑定),发生在同一类内(或子类继承父类方法后定义同名不同参方法)。判定标准只看方法名和参数列表(个数、类型、顺序),与返回类型、访问修饰符、异常列表均无关。例如:
class Demo {
void f(int a) {}
void f(String s) {} // 参数类型不同,重载
void f(int a, int b) {} // 参数个数不同,重载
int f(int a, String s) { return 0; } // 顺序不同,重载
}子类继承父类后,若定义与父类方法同名但参数不同,对子类而言也构成重载。重载的意义在于让同一语义操作适配不同参数类型,提升 API 的易用性。
【记忆锚点】 「重载看参数,重写看签名;重载是静态,重写是动态」。
【易混对比】
| 维度 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 发生位置 | 同一类或父子类 | 父子类之间 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回类型 | 无关 | 相同或协变子类 |
| 访问权限 | 无关 | 不能更严格 |
| 异常声明 | 无关 | 不能更宽 |
| 绑定时机 | 编译期(静态绑定) | 运行期(动态绑定) |
【自测】 以下代码是否构成重载?
class A {
int f(int a) { return 0; }
long f(int a) { return 0L; }
}答:不构成重载,编译报错。参数列表完全相同,仅返回类型不同无法区分方法。与第 J11 题连考。
【知识关联】
- 同库关联:与 J12(重写)、J13(多态)组成 OOP 核心题群;与 J71(泛型擦除影响重载选择)进阶连考。
- 实现层:重载在编译期决定(静态分派,invokevirtual 常量池指向具体描述符);重写是运行期动态分派。字节码方法描述符含参数类型,故重载靠参数列表区分。
- 面试追问:① 重载能否只靠返回值区分?②
null作为实参如何选择重载?
【拓展延伸】
- 变式问法:给出两个仅返回值不同的方法问能否编译;问自动装箱与重载优先级(精确匹配优先于装箱/可变参数)。
- 版本差异:语义稳定;Kotlin 支持默认参数/命名参数,部分场景可替代重载。
- 工程注意点:重载参数顺序尽量保持直觉一致;避免「一长串几乎相同的重载」;文档写清每个重载语义差异。
J12 · 知识点:重写(Override)规则
【题目】子类重写父类方法时,下列说法正确的是?
A. 访问权限可以比父类更严格 B. 返回类型可以任意改变 C. 抛出的受检异常范围不能比父类更宽 D. 重写方法可以是 static 的
答案:C
【考点】重写规则的“两同两小一大”。
【结论】选 C。子类重写时,抛出的受检异常范围不能比父类更宽。
【逐项辨析】
- A错:访问权限只能比父类更宽松(放大),不能更严格。例如父类为 protected,子类可改为 public,反之则编译报错。
- B错:返回类型必须相同,或是父类返回类型的子类(协变返回,JDK5 起支持),不能任意改变。
- C正确:受检异常必须是父类方法声明异常的子类或同类,不能抛出更宽泛的父类异常。
- D错:static 方法不能被重写。子类定义与父类同名同参的 static 方法属于“隐藏(hide)”,调用时按引用类型静态绑定。
【知识点】 重写遵循“两同两小一大”原则:
- 两同:方法名相同、参数列表相同;
- 两小:返回类型不能更大(协变允许子类返回类型)、受检异常不能更宽;
- 一大:访问权限不能更小(只能放大)。
协变返回类型自 JDK5 起支持,允许子类重写方法返回父类返回类型的子类。父类的 private 方法对子类不可见,子类定义同名方法属于新方法,不构成重写。final 方法不可被重写。重写是多态的核心机制,由 JVM 在运行期根据实际对象类型动态绑定方法版本。
【记忆锚点】 「两同两小一大:同名同参,返异小,异常小,权限大」。
【易混对比】
- 隐藏(Hide)vs 重写(Override):static 方法、private 方法、final 方法均不能被重写;子类对 static 方法同名同参定义叫隐藏,调用时看引用类型(静态绑定),重写看实际对象类型(动态绑定)。
- 换问法:若父类方法声明
throws IOException,子类重写可以声明throws FileNotFoundException(子类异常,合法),但不能声明throws Exception(父类异常,非法)。
【自测】 以下代码能否编译通过?
class Parent {
protected void f() throws IOException {}
}
class Child extends Parent {
void f() throws FileNotFoundException {}
}答:不能。子类重写将访问权限从 protected 缩小为默认(包私有),违反“一大”原则;虽然 FileNotFoundException 是 IOException 子类满足“异常小”,但权限缩小已导致编译失败。与第 J12 题连考。
【知识关联】
- 同库关联:与 J11(重载)、J13(多态)、J18(访问修饰符影响重写)、J19(static 隐藏非重写)强相关。
- 实现层:invokevirtual/
invokeinterface运行期查 vtable;返回类型协变(JDK 5+)允许子类返回更具体类型;异常不能比父类更宽。 - 面试追问:① 构造器能否重写?② private/final/static 方法能否重写?
【拓展延伸】
- 变式问法:问重写时访问权限能否变小(不能);问
@Override注解作用(编译检查)。 - 版本差异:JDK 5 支持协变返回值;JDK 8 接口 default 可被重写(J17)。
- 工程注意点:始终加
@Override;遵循里氏替换;子类重写不要改变语义只扩展行为。
J13 · 知识点:多态
【题目】给定 class Animal {}、class Dog extends Animal {}、Animal a = new Dog();,下列说法正确的是?
A. 编译期 a 的类型是 Dog B. 该代码无法编译 C. a 可以直接调用 Dog 特有的方法 D. 运行期 a 的实际对象类型是 Dog
答案:D
【考点】多态的“编译看左边(静态类型),运行看右边(实际类型)”。
【结论】选 D。Animal a = new Dog() 运行时实际创建的是 Dog 对象,a 的实际类型为 Dog。
【逐项辨析】
- A错:编译期看引用声明类型,a 的编译期类型是 Animal,不是 Dog。
- D正确:运行期看实际对象类型,new Dog() 创建的是 Dog 实例,a 的实际对象为 Dog。
- C错:a 的编译期类型是 Animal,只能调用 Animal 中声明的方法;调用 Dog 特有方法必须向下强转
((Dog) a).method()。 - B错:Dog 是 Animal 子类,向上转型自动完成且安全,代码完全可以编译。
【知识点】 多态的核心机制是“编译看左边,运行看右边”。编译器根据引用类型(静态类型)决定可调用哪些方法;JVM 根据实际对象类型(动态类型)决定调用哪个具体实现。向上转型(Upcasting)将子类引用赋给父类引用,自动完成且安全;向下转型(Downcasting)需显式强转,可能抛出 ClassCastException,建议先用 instanceof 判断。
多态的意义在于统一接口、隔离变化——同一引用调用同一方法,实际执行的是对象真实类型的方法。这也是“开闭原则”的基础:对扩展开放(新增子类),对修改关闭(无需改动基于父类引用的调用代码)。
【记忆锚点】 「编译看左边,运行看右边;上调自动,下转要强」。
【易混对比】
- 静态绑定 vs 动态绑定:private、static、final 方法在编译期即可确定调用版本(静态绑定);实例方法根据运行时类型决定(动态绑定)。
- 换问法:
Animal a = new Dog(); a.eat(),若 Animal 和 Dog 都有 eat(),实际执行 Dog.eat();若 Dog 独有 bark(),直接a.bark()编译报错。
【自测】 以下代码输出什么?
class Animal { void speak() { System.out.print("A"); } }
class Dog extends Animal { void speak() { System.out.print("D"); } }
Animal a = new Dog();
a.speak();答:输出 D。运行时看实际类型 Dog,调用 Dog 的 speak()。与第 J13 题连考。
【知识关联】
- 同库关联:与 J11/J12、J21(instanceof)、J19(static 无多态)构成面向对象主干;与 P57(Python 鸭子类型)对照——Python 无编译期多态约束。
- 实现层:动态绑定依赖对象头类型指针与 vtable;字段访问没有多态(按引用类型静态绑定),只有实例方法有多态。
- 面试追问:① 为何字段没有多态?② 向上转型/向下转型的编译与运行期规则?
【拓展延伸】
- 变式问法:父引用指向子对象,调用被重写方法输出什么;问多态与设计模式(策略、模板方法)的关系。
- 版本差异:语义稳定;Python/JS 是鸭子类型,运行期才失败。
- 工程注意点:面向抽象编程减少 if-else 类型分派;向下转型前先 instanceof(J21);避免在父类构造器中调用可被重写的方法。
J14 · 知识点:构造器特性
【题目】关于构造器(Constructor)的说法,正确的是?
A. 构造器可以被继承 B. 构造器可以被重写 C. 构造器可以被重载 D. 构造器有返回类型
答案:C
【考点】构造器的命名、继承、重载特性。
【结论】选 C。构造器可以被重载,但不能被继承或重写,也没有返回类型。
【逐项辨析】
- A 错:构造器不能被继承。子类不继承父类构造器,但可通过
super(...)调用父类构造器完成父类部分初始化。 - B 错:构造器不能被重写。重写要求方法签名相同且存在继承关系,而构造器名必须与类名一致,父子类构造器名称本就不同。
- C 正确:同一类中可定义多个参数列表不同的构造器,构成重载,通过
this(...)相互调用以减少代码重复。 - D 错:构造器没有返回类型,连 void 都不能写;写了 void 就变成普通方法而非构造器。
【知识点】 构造器是特殊的实例方法,名称与类名完全一致,无返回类型。编译器默认提供无参构造器,但一旦显式定义了任何构造器,默认无参构造器即消失。子类构造器首行若无显式 super() 或 this(),编译器会自动插入 super() 调用父类无参构造器——若父类没有无参构造器,则子类必须显式调用父类带参构造器,否则编译报错。
构造器重载通过参数个数、类型区分,常用于提供带默认值的多种构造方式。构造器不能被 static、final、abstract、synchronized 修饰,因为它负责对象创建这一特殊生命周期阶段。
【记忆锚点】 「构造器名同类名,无返回,可重载,不继承」。
【易混对比】
- 构造器 vs 普通方法:构造器在对象实例化时自动调用,用于初始化;普通方法需显式调用。构造器不返回任何值,普通方法可返回 void 或具体类型。
- 换问法:父类只有带参构造器时,子类必须怎么做?——必须在子类构造器第一行显式写
super(参数),否则编译报错。
【自测】 以下代码能否编译通过?
class A { A(int x) {} }
class B extends A { B() {} }答:不能。A 显式定义了带参构造器,无参构造器不再默认提供;B 的构造器隐式调用
super()找不到A(),编译报错。应改为B() { super(0); }。与第 J14 题连考。
【知识关联】
- 同库关联:与 J15(super/this)、J20(初始化顺序)同属构造与初始化题群;与 J01(字段默认值)衔接。
- 实现层:构造器编译为
<init>方法;若未显式调用 super(),javac 插入父类无参<init>;与类初始化方法<clinit>区分。 - 面试追问:① 构造器能否被继承/重写?② 抽象类的构造器有何作用?
【拓展延伸】
- 变式问法:问构造器返回值(无);问私有构造器用途(单例/工具类)。
- 版本差异:record(JDK 16+)自动生成规范构造器;enum 构造器必 private(J72)。
- 工程注意点:避免构造器中泄漏 this;复杂对象用 Builder;不要在构造器中调用可重写方法。
J15 · 知识点:super() 与 this()
【题目】关于子类构造器中 super() 和 this() 的调用规则,正确的是? A. 可以同时出现在构造器第一行 B. 可以出现在构造器任意位置 C. 必须出现在构造器第一行,且两者只能出现一个 D. this() 可以出现在子类构造器的任意位置
答案:C
【考点】构造器链:super() 调父类构造器、this() 调本类其他构造器,均须位于第一行且互斥。
【结论】选 C。super() 和 this() 都必须作为构造器的第一条语句,且二者互斥,只能出现一个。
【逐项辨析】
- A错:两者不能同时出现,因为每个都要求是第一行,语法上无法并存。
- C正确:
super(...)调父类构造器,this(...)调本类其他构造器,均须为第一行且只能出现一个。 - B错:不能出现在任意位置,必须是构造器的第一条语句,以确保对象初始化按正确顺序进行。
- D错:
this()同样必须位于第一行,不能出现在任意位置。
【知识点】 构造器调用链规则:子类实例化时,必须先完成父类部分的初始化。super(...) 用于显式调用父类指定构造器;this(...) 用于显式调用本类其他构造器以减少代码重复。若子类构造器第一行既无 super 也无 this,编译器自动插入无参 super()。
父类构造器执行顺序沿继承链自顶向下:Object → 顶层父类 → ... → 子类。这一机制确保对象初始化时,父类字段先于子类字段完成构造。this() 指向的本类其他构造器最终仍需通过 super() 完成父类构造,因此整个链条的根部一定是父类构造器。
【记忆锚点】 「super 调父,this 调己,都只能站第一,二者水火不容」。
【易混对比】
- super 关键字的双重身份:
super()是构造器调用,必须第一行;super.method()是调用父类被重写的方法,可在任意位置使用。 - 换问法:子类构造器第一行写
this()后,还会调用父类构造器吗?——会,this()指向的本类其他构造器最终仍需通过super()完成父类构造。
【自测】 以下代码输出顺序是什么?
class A { A() { System.out.print("A"); } }
class B extends A {
B() { this(1); System.out.print("B"); }
B(int x) { System.out.print("C"); }
}
new B();答:ACB。B() 调用
this(1),B(int) 第一行隐式super()调用 A() 输出 A,然后输出 C,回到 B() 输出 B。与第 J15 题连考。
【知识关联】
- 同库关联:与 J14、J20(父类静态→父类实例→子类实例)、J19(static 与 this)强相关。
- 实现层:
this()/super()必须是构造器第一条语句;字节码中<init>开头要么aload0;invokespecial 父类.<init>要么aload0;invokespecial 本类其他.<init>。 - 面试追问:① 为何 this/super 不能同时出现在同一构造器?② 构造器链会不会成环?
【拓展延伸】
- 变式问法:给出父子构造器代码问输出顺序;问
this(x)与super()互斥规则。 - 版本差异:语义稳定;record/enum 有额外约束。
- 工程注意点:把公共初始化抽到
init()会破坏 final 语义,优先构造器链;用this(默认值)提供便利构造器。
J16 · 知识点:抽象类与接口
【题目】关于抽象类和接口,下列说法正确的是?
A. 抽象类不能有构造器 B. 一个类可以继承多个抽象类 C. JDK8 之前,接口中的方法全部是抽象方法 D. 抽象类不能有非抽象方法
答案:C
【考点】抽象类与接口的结构差异、接口方法的版本演进。
【结论】选 C。JDK8 之前,接口中的方法隐式为 public abstract,全部是抽象方法。
【逐项辨析】
- A 错:抽象类可以有构造器,供子类
super()调用,以初始化抽象类中定义的字段。 - C 正确:JDK8 之前接口方法只能是 public abstract;JDK8 起新增 default 和 static 方法,JDK9 起支持 private 方法。
- B 错:Java 类是单继承,一个类最多只能 extends 一个父类(无论是否抽象),但可实现多个接口。
- D 错:抽象类可以包含非抽象方法(已实现的方法),甚至可以没有抽象方法(但如此设计通常无意义)。
【知识点】 抽象类与接口的设计定位不同:抽象类是“is-a”关系,用于提取子类共性,可含字段、构造器、实现方法;接口是“has-a”或“can-do”能力契约。版本演进:
| 版本 | 接口新增能力 |
|---|---|
| JDK 1.0 | 只能有 public abstract 方法 |
| JDK 5 | 支持嵌套枚举类型 |
| JDK 8 | 新增 default、static 方法(有方法体) |
| JDK 9 | 新增 private 方法(接口内部复用) |
抽象类不能实例化(new 会编译报错),子类必须实现所有抽象方法(除非子类也是抽象类)。接口中的变量隐式为 public static final 常量,方法在 JDK8 前隐式为 public abstract。
【记忆锚点】 「抽象类是半成品,有构造可有实现;接口本是纯契约,JDK8 之后有默认」。
【易混对比】
| 特性 | 抽象类 | 接口(JDK8+) |
|---|---|---|
| 构造器 | 有 | 无 |
| 继承/实现 | 单继承(extends) | 多实现(implements) |
| 方法实现 | 可以有 | default / static / private 可以有 |
| 字段 | 实例变量、常量均可 | 只能是 public static final |
| 访问修饰符 | 任意 | 默认 public |
| 设计意图 | 提取共性(is-a) | 定义能力(can-do) |
【自测】 JDK8 接口中以下哪种方法不能有方法体?(版本口径:private 接口方法是 JDK9 才引入的特性——javac --release 8 对私有接口方法直接报「-source 8 中不支持 私有接口方法」,故严格限定 JDK8 时 D 项在语言层面并不存在;按 JDK9+ 口径四个选项才同时成立,答案不变) A. default 方法 B. static 方法 C. 普通抽象方法 D. private 方法
答:C。普通抽象方法仍只有声明无实现;default(JDK8)、static(JDK8)、private(JDK9)都可以有方法体。与第 J16 题连考。
【知识关联】
- 同库关联:与 J17(default 方法)、J75(默认方法继承链)直接连考;与 J12(重写)、J18(访问修饰符)相关。
- 实现层:接口方法默认 public abstract(JDK 7-);字段隐式 public static final。JDK 8+ 可有 default/static;JDK 9+ 可有 private 方法。抽象类可有实例字段与构造器。
- 面试追问:① 何时选抽象类何时选接口?② 接口多实现 vs 类单继承如何影响设计?
【拓展延伸】
- 变式问法:问接口能否有构造器/实例变量(不能);问抽象类能否全为具体方法。
- 版本差异:JDK 7 及以前接口不能有实现;JDK 8 default/static;JDK 9 private interface methods,便于抽取公共逻辑。
- 工程注意点:SPI/策略扩展优先接口;共享状态或模板方法用抽象类;避免「接口爆炸」,可用 default 提供向后兼容扩展(J17)。
J17 · 知识点:接口 default 方法
【题目】关于 JDK8 接口的 default 方法,下列说法错误的是?
A. 可以有方法体 B. 实现类必须重写它 C. 一个类实现的两个接口若有同名 default 方法,该类必须重写以解决冲突 D. 它解决了接口扩展时的兼容性问题
答案:B
【考点】接口默认方法机制与“菱形冲突”解决规则。
【结论】选 B。实现类不是必须重写 default 方法,可以选择直接继承接口的默认实现。
【逐项辨析】
- A 说法正确(非答案):default 方法的核心特征就是拥有方法体,提供默认实现。
- B 说法错误(应选):实现类可以选择不重写 default 方法,直接继承默认实现即可。
- C 说法正确(非答案):两个接口存在同名同参的 default 方法时产生菱形冲突,实现类必须重写以消除歧义。
- D 说法正确(非答案):default 方法的设计初衷正是让接口平滑演进,旧实现类无需修改即可兼容新增方法。
【知识点】 接口 default 方法(JDK8 引入)打破了“接口不能有实现”的传统限制,使接口可以在不破坏现有实现类的前提下新增方法。菱形冲突解决规则:当类实现的多个接口存在同名同参的 default 方法时,编译器无法决定继承哪个,强制实现类必须重写;重写后可通过 接口名.super.方法名() 显式调用指定接口的默认实现。
static 方法属于接口本身,不继承,不产生冲突。类优先原则——若父类和父接口都有同名方法,类的方法优先于接口 default 方法被继承。
【记忆锚点】 「default 有默认,实现可继承;多接口同名冲突,必须重写摆平」。
【易混对比】
- default vs abstract:default 有方法体、可选重写;abstract 无方法体、必须重写。
- default vs static:default 方法可被实现类继承和重写;static 方法只属于接口,通过接口名直接调用,实现类不继承。
- 换问法:若父类和父接口都有同名方法,子类调用时优先用哪个?——类的方法优先于接口 default 方法。
【自测】 以下代码能否编译通过?
interface A { default void f() { System.out.println("A"); } }
interface B { default void f() { System.out.println("B"); } }
class C implements A, B {}答:不能。A 和 B 的 default 方法 f() 冲突,C 必须重写 f(),或在重写中通过
A.super.f()/B.super.f()指定调用其中一个。与第 J17 题连考。
【知识关联】
- 同库关联:与 J16、J75(实现类继承链中 default 冲突/类优先)、J12(可被重写)同一题群。
- 实现层:default 方法进入接口的 vtable;菱形冲突时编译器强制实现类生成桥接/重写;
Interface.super.m()生成 invokespecial 指定调用。 - 面试追问:① 类优先原则是什么?② 两个接口同名 default,实现类不重写会怎样?
【拓展延伸】
- 变式问法:问「实现类必须重写 default」为何是错误说法(可选);问 static 接口方法是否被继承(否)。
- 版本差异:JDK 8 引入;JDK 9 增加 private interface method;后续版本语义稳定。
- 工程注意点:用 default 给老接口加方法时避免破坏二进制兼容;冲突时显式重写并文档化;不要用 default 塞业务逻辑,适合简单默认实现。
J18 · 知识点:访问修饰符
【题目】以下访问修饰符按可见范围从大到小排列,正确的是?
A. protected > public > 默认 > private B. public > 默认 > protected > private C. public > protected > 默认(package-private)> private D. public > protected > private > 默认
答案:C
【考点】四个访问修饰符的可见性边界。
【结论】选 C。可见性从大到小为:public > protected > 默认(package-private)> private。
【逐项辨析】
- C 正确:public 全开放;protected 同包 + 所有子类(含不同包子类);默认仅同包;private 仅本类。
- B 错:protected 范围大于默认,因为 protected 允许不同包的子类访问,而默认不行。
- A 错:public 范围最大,protected 不可能大于 public。
- D 错:默认范围大于 private(同包类可访问),private 不可能排在默认之前。
【知识点】 Java 访问控制是封装(Encapsulation)的核心实现手段。四个修饰符的精确边界:
| 修饰符 | 本类 | 同包 | 不同包子类 | 不同包非子类 |
|---|---|---|---|---|
| public | ✓ | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✓ | ✗ |
| 默认 | ✓ | ✓ | ✗ | ✗ |
| private | ✓ | ✗ | ✗ | ✗ |
protected 对“不同包的子类”可见,但对“不同包的非子类”不可见——这是与默认修饰符的关键差异。内部类可以访问外部类的私有成员,这是编译器生成合成访问器方法实现的。
【记忆锚点】 「公保默私,范围递减;protected 跨包子类,默认寸步不离」。
【易混对比】
- 换问法:protected 成员能否被同包非子类访问?——可以,protected 包含同包所有类,不限于子类。
- 进阶考法:private 方法在子类中完全不可见,子类定义同名方法属于新方法,不构成重写。
【自测】 某类的 protected 方法,在另一个包中的非子类里能否访问?
答:不能。protected 对另一个包中的非子类不可见,此时与 private 效果相同。与第 J18 题连考。
【知识关联】
- 同库关联:与 J12(重写访问权限不能更小)、J16(接口成员默认 public)相关。注:本库没有「private 与封装」专题题(J09 讲的是 final 关键字),别错引。
- 实现层:访问控制在编译期检查;跨包 private 访问由编译器生成合成 accessor(NestHost/Nestmates,JDK 11+ 优化内部类私有访问)。
- 面试追问:① protected 是否等于「子类可见」?② 为什么内部类能访问外部类 private?
【拓展延伸】
- 变式问法:排序四修饰符;问同包非子类能否访问 protected(能)。
- 版本差异:模块系统(JDK 9+)增加 module 导出约束,public 也未必跨模块可见。
- 工程注意点:最小可见性原则;对外 API 谨慎 protected;跨模块库注意 module-info exports。
J19 · 知识点:static 关键字
【题目】关于 static 关键字的说法,错误的是?
A. static 方法中不能直接访问非静态成员 B. static 变量属于类,所有实例共享 C. static 方法可以被重写 D. 静态代码块在类加载时执行
答案:C
【考点】static 成员的类归属、static 方法与重写的关系。
【结论】选 C。static 方法不能被重写,子类定义与父类同名同参的 static 方法属于“隐藏(hide)”。
【逐项辨析】
- A 说法正确(非答案):static 方法没有 this 引用,只能直接访问静态成员;访问非静态成员需先创建对象。
- B 说法正确(非答案):static 变量(类变量)在方法区存储,所有实例共享同一份数据。
- C 说法错误(应选):static 方法不能被重写。重写是多态的基础,需要动态绑定;static 方法在编译期静态绑定。
- D 说法正确(非答案):静态代码块在类加载的初始化阶段执行,且由于类只加载一次,静态代码块也只执行一次。
【知识点】 static 修饰的成员属于类而非实例。类加载时,静态变量分配内存并初始化(准备阶段赋默认值,初始化阶段赋真实值),静态代码块按声明顺序执行。static 方法通过类名调用,无 this 引用,因此不能访问非静态成员,也不能被重写。
子类对父类 static 方法的同名同参定义称为隐藏(hide),编译器根据引用类型决定调用版本。例如:
class A { static void f() { System.out.print("A"); } }
class B extends A { static void f() { System.out.print("B"); } }
A obj = new B();
obj.f(); // 输出 A,按引用类型 A 静态绑定【记忆锚点】 「static 属类不属对象,无 this 不访问实例,方法不能重写只能藏」。
【易混对比】
- 重写(Override)vs 隐藏(Hide):实例方法同名同参是重写,运行期动态绑定;static 方法同名同参是隐藏,编译期静态绑定。
- 静态代码块 vs 实例代码块 vs 构造器:静态代码块类加载时执行一次;实例代码块每次构造前执行;构造器每次构造时执行。
- 换问法:static 方法中能否使用 this 或 super?——不能,static 上下文不存在当前实例。
【自测】 以下代码输出什么?
class A { static void f() { System.out.print("A"); } }
class B extends A { static void f() { System.out.print("B"); } }
A obj = new B();
obj.f();答:输出 A。static 方法隐藏按引用类型(A)静态绑定,不是重写。与第 J19 题连考。
【知识关联】
- 同库关联:与 J12(实例方法重写)、J20(静态块执行顺序)、J63(方法区/元空间存 static)、J68(类加载初始化)强相关。
- 实现层:static 方法无 this,invokestatic 静态绑定;子类同名同参 static 为 hide,按引用类型编译期选择。类变量在准备阶段赋零值、初始化阶段赋真实值。
- 面试追问:① 重写与隐藏的本质区别?② 静态方法能否被反射动态调用?
【拓展延伸】
- 变式问法:父引用指向子对象调用 static 方法输出父类版本;问静态块与 main 谁先执行(静态块在类初始化时)。
- 版本差异:语义稳定;Java 无静态扩展方法(用工具类或 JDK 8 接口 static)。
- 工程注意点:工具类
private构造器 + static 方法;避免 static 可变全局状态(难测、线程不安全);测试中用 @StaticMock 要谨慎。
J20 · 知识点:初始化顺序
【题目】执行 new B(); 时,下列代码的输出顺序是?
class A {
static { System.out.print("A"); }
{ System.out.print("B"); }
A() { System.out.print("C"); }
}
class B extends A {
static { System.out.print("D"); }
{ System.out.print("E"); }
B() { System.out.print("F"); }
}A. ABDCEF B. ADBECF C. ADBCEF D. DABCEF
答案:C
【考点】类初始化顺序:静态块(父→子)→ 实例块+构造器(父→子)。
【结论】选 C。输出顺序为 ADBCEF。
【推导过程】
- 类加载阶段:先初始化父类 A,执行 A 的静态代码块,输出 A。
- 继续类加载阶段:初始化子类 B,执行 B 的静态代码块,输出 D。
- 实例化阶段:创建 B 的对象,先构造父类 A 的部分。执行 A 的实例代码块,输出 B;再执行 A 的构造器,输出 C。
- 实例化阶段:继续构造子类 B 的部分。执行 B 的实例代码块,输出 E;再执行 B 的构造器,输出 F。
- 综合顺序:A → D → B → C → E → F,即 ADBCEF。
【逐项辨析】
- C ADBCEF 正确:按“先静态后实例,先父后子”规则推导。
- B ADBECF 错:混淆了实例代码块与构造器的执行顺序。同一类中实例代码块总是在构造器之前执行。
- A ABDCEF 错:它把父类的实例块与构造器(B、C)排到了子类静态块 D 之前。
new B()会先完成两个类的静态初始化(父类 A → 子类 D),之后才进入实例初始化(父类实例块 B → 父类构造 C → 子类实例块 E → 子类构造 F),所以 D 必须紧跟 A、排在 B 之前。 - D DABCEF 错:子类静态初始化在父类静态初始化之后,D 不能在 A 之前。
【知识点】 Java 对象初始化遵循严格的顺序规则:
- 静态初始化(父 → 子):静态变量赋值 + 静态代码块,按源码顺序执行,类加载时仅执行一次;
- 实例初始化(父 → 子):实例变量赋值 + 实例代码块,按源码顺序执行,每次 new 都执行;
- 构造器(父 → 子):实例代码块之后执行。
口诀:“先静态后实例,先父后子”。静态成员随类加载而初始化;实例成员随对象创建而初始化。若构造器首行有 this(...),则先执行本类其他构造器,但最终仍需沿继承链完成父类构造。
【记忆锚点】 「先静后实,先父后子;静只一次,实每 new 一次」。
【易混对比】
- 静态代码块 vs 实例代码块:静态在类加载时执行且仅一次;实例在每次构造对象前执行。
- 父类构造器调用:子类构造器首行隐式
super()确保父类实例代码块和构造器先于子类执行。 - 换问法:若父类静态代码块中 new 了子类对象,输出顺序会更复杂——此时父类加载未完毕就会触发子类加载,但 JVM 保证每个类的初始化只进行一次。
【自测】 以下代码输出什么?
class A {
static { System.out.print("1"); }
{ System.out.print("2"); }
A() { System.out.print("3"); }
}
class B extends A {
static { System.out.print("4"); }
{ System.out.print("5"); }
B() { System.out.print("6"); }
public static void main(String[] args) { new B(); new B(); }
}答:1423562356。第一次 new B() 触发类加载(1、4)和实例化(2、3、5、6);第二次 new B() 类加载已完毕,只走实例化(2、3、5、6)。与第 J20 题连考。
【知识关联】
- 同库关联:与 J14/J15(构造器链)、J19(静态块)、J01(默认值)、J24(常量池,static final 编译期)构成初始化全景。
- 实现层:类初始化
<clinit>合并静态字段与静态块,按文本顺序;实例初始化在<init>中:父类实例块/构造 → 子类实例块/字段 → 子类构造器。 - 面试追问:① 子类静态块与父类静态块谁先?② 构造器中调用重写方法会发生什么?
【拓展延伸】
- 变式问法:给出多层继承 + 静态/实例块代码问完整输出顺序。
- 版本差异:语义稳定;record 的紧凑构造器简化字段校验。
- 工程注意点:避免在构造器中调用可重写方法(子类字段可能尚未赋值);复杂初始化用 @PostConstruct(Spring S16)或工厂方法。
J21 · 知识点:instanceof 与类型转换
【题目】执行 String s = "a"; Object o = s;,下列说法正确的是? A. 代码运行时会报错 B. o instanceof Object 为 false C. 直接通过 o 调用 String 的特有方法,无需强转 D. o instanceof String 为 true
答案:D
【考点】instanceof 基于运行时类型判断;向上转型自动完成。
【结论】选 D。向上转型后,o 的运行时类型仍是 String,因此 o instanceof String 为 true。
【逐项辨析】
- D正确:o 的运行时实际对象是 String 实例,instanceof 基于运行时类型判断,结果为 true。
- B错:String 是 Object 的子类,o 的运行时类型是 String,故
o instanceof Object也为 true。 - C错:o 的编译期类型是 Object,只能调用 Object 声明的方法;String 特有方法(如 substring() 等)不能直接调用,需向下强转
(String) o。 - A错:向上转型是 Java 合法语法,自动完成且安全,不会报错。
【知识点】 instanceof 是运行时类型检查运算符,判断对象是否为某类或其子类的实例。向上转型(Upcasting)将子类引用赋给父类引用,自动完成,安全;向下转型(Downcasting)将父类引用赋给子类引用,需显式强转,不安全,可能抛出 ClassCastException。
Java 16 起引入模式匹配 instanceof:if (o instanceof String s) 可直接在条件成立时使用 s,无需额外强转。类型转换的原则:编译看引用类型,运行看实际类型。null instanceof AnyClass 永远为 false,因为 null 不是任何类的实例。
【记忆锚点】 「instanceof 看实际,向上自动向下强,模式匹配省强转」。
【易混对比】
- instanceof vs isInstance():instanceof 是运算符;Class.isInstance() 是反射方法,效果等价但适用场景不同。
- 编译期检查:若编译器能确定某引用与某类型无继承关系(如
"abc" instanceof Integer),直接编译报错。 - 换问法:
null instanceof String结果是什么?——false,null 不是任何类的实例。
【自测】 以下代码能否编译通过?
Object o = "hello";
Integer i = (Integer) o;答:能编译通过(编译期只看引用类型 Object,可以转为 Integer),但运行时报 ClassCastException,因为实际对象是 String,不能转为 Integer。与第 J21 题连考。
【知识关联】
- 同库关联:与 J13(多态)、J12(重写)、J71(泛型擦除后无法 instanceof 泛型参数)直接相关;与 J22(equals)在对象比较题群相邻。
- 实现层:instanceof 在字节码为 instanceof 指令,运行期查类继承关系;向下转换 checkcast 失败抛 ClassCastException。
- 面试追问:① 子类 instanceof 父类为何为 true?② 接口类型 instanceof 的规则?
【拓展延伸】
- 变式问法:null instanceof X 恒为 false;问强制转换失败时机(运行期)。
- 版本差异:JDK 16+ 正式引入 pattern matching for instanceof(
if (obj instanceof String s)),减少显式强转。 - 工程注意点:优先模式匹配;避免过度向下转型(可能破坏多态);集合泛型用通配符减少强转。
J22 · 知识点:equals 与 hashCode
【题目】重写 equals() 时必须重写 hashCode(),最根本的原因是? A. 只是为了提高性能 B. 否则代码无法编译 C. 保证相等的对象在 HashSet/HashMap 中哈希值一致,否则 equals 相等却可能无法去重、无法取出 D. 两者没有关系
答案:C
【考点】equals/hashCode 约定(约定一:equals 相等的对象 hashCode 必须相等)及其在哈希集合中的作用。
【结论】选 C。重写 equals 必须重写 hashCode,以保证相等对象在 HashSet/HashMap 等哈希集合中行为正确。
【逐项辨析】
- B错:不重写 hashCode 不会导致编译错误,代码可以编译通过,但运行时在哈希集合中行为异常。
- C正确:HashSet/HashMap 先按 hashCode 定位桶,再用 equals 比较;若 equals 相等但 hashCode 不同,对象会被放入不同桶,导致去重失败、get 取不到值。
- A错:hashCode 的主要目的不是提高性能,而是保证对象在哈希表中的正确性;良好的 hashCode 分布才能提升性能。
- D错:equals 和 hashCode 有严格约定关系,equals 相等则 hashCode 必须相等(反之不成立)。
【知识点】 Java 规范对 hashCode 有三条约定:
- 同一对象多次调用 hashCode 应返回相同值(对象未修改时);
- 两对象 equals 相等,则 hashCode 必须相等;
- equals 不等,hashCode 不要求必须不等(但不相等可提高哈希表性能)。
哈希集合(HashSet、HashMap、LinkedHashSet 等)的工作流程:先计算 hashCode 定位数组桶索引,再在桶内(链表 / 红黑树)用 equals 逐一比较。违反约定会导致:equals 相等的对象被存入不同桶,Set 无法去重,Map 用 key 取不到值。IDE 和 Lombok 的 @EqualsAndHashCode 可自动生成符合规范的实现。
【记忆锚点】 「equals 相等 hashCode 必等,hashCode 是桶号 equals 是门牌」。
【易混对比】
- == vs equals:== 比较引用地址;equals 默认行为是 ==,但 String、包装类等已重写为内容比较。
- hashCode 实现:默认基于对象内存地址(Object.native 方法);重写时应使用 equals 中参与比较的全部字段计算,保证一致性。
- 换问法:只重写 hashCode 不重写 equals 会有问题吗?——也会有,因为 equals 仍按 == 比较,逻辑相等的对象可能不被视为相等。
【自测】 以下代码中 HashSet 的元素个数是多少?
class Person {
String name;
Person(String name) { this.name = name; }
public boolean equals(Object o) {
return o instanceof Person && ((Person)o).name.equals(name);
}
// 未重写 hashCode
}
Set<Person> set = new HashSet<>();
set.add(new Person("A"));
set.add(new Person("A"));
System.out.println(set.size());答:2。两个 Person 的 name 都是 "A",equals 为 true,但继承自 Object 的 hashCode 基于地址计算,两个 new 出来的对象地址不同,hashCode 不同,被放入不同桶,Set 无法去重。与第 J22 题连考。
【知识关联】
- 同库关联:与 J34(HashMap 依赖 hashCode/equals)、J37(HashSet)、J28(Integer equals)构成相等性题群;与 P05(is 与 ==)对照。
- 实现层:HashMap 先比 hash 再 equals;重写 equals 必须重写 hashCode 以保证「相等对象相同 hash」契约。
- 面试追问:① 为何重写 equals 必须重写 hashCode?② == 与 equals 分别比较什么?
【拓展延伸】
- 变式问法:两个内容相同的 new String("a") 的 == 与 equals;或作为 HashMap key 时的查找失败场景。
- 版本差异:JDK 7+ Objects.equals/null-safe;record 自动生成基于组件的 equals/hashCode。
- 工程注意点:用 Objects.hash;可变对象作 key 有风险;调试时断点看 equals 契约。