【Java +AI |基础篇day6、7、8 OOP高级 继承 多态 抽象 代码块 内部类 函数式编程】
这期也是上强度了 小💡就老老实实多看几遍视频 老💡就老老实实被八股拷打 😊
一、继承(OOP 三大特征之二)—— 从代码复用到底层逻辑
继承是 Java 实现代码纵向复用的核心机制,通过建立类的父子关系,让子类复用父类的共性逻辑,同时扩展自身特有功能。
1. 继承的底层本质与语法
核心定义:子类(派生类)通过
extends关键字继承父类(基类 / 超类),本质是子类对象包含父类的成员变量和方法(内存中子类对象由 “父类部分 + 子类部分” 组成)。语法格式
:
java
// 父类(共性抽取) class Parent {String name; // 父类成员变量public void eat() { // 父类成员方法System.out.println(name + "吃饭");} } // 子类(继承+扩展) class Child extends Parent {int age; // 子类特有成员变量public void study() { // 子类特有方法System.out.println(name + "(" + age + "岁)学习");} }内存模型:创建子类对象
Child c = new Child();时,堆内存中先初始化父类部分(name等),再初始化子类部分(age等),栈内存的引用c指向整个子类对象。
2. 权限修饰符的底层控制逻辑
权限修饰符通过 JVM 访问校验机制 限制成员的可访问范围,直接决定子类能否继承父类成员:
| 修饰符 | 本类 | 同一包 | 子孙类(跨包) | 任意类 | 继承可见性原理 |
|---|---|---|---|---|---|
private | √ | 编译期直接限制访问,字节码中标记为私有,子类无法获取成员引用 | |||
| 缺省(无修饰) | √ | √ | 仅包内可见,跨包子类编译时无法解析成员符号 | ||
protected | √ | √ | √ | 跨包子类可通过 “继承上下文” 访问(如子类方法中直接调用 super.成员),但无法通过父类对象访问 | |
public | √ | √ | √ | √ | 无访问限制,编译期不做限制,运行时可通过任意对象访问 |
关键案例:跨包子类访问父类 protected 成员
java
// 包1:父类
package com.parent;
public class Parent {protected String name = "父类名字";
}
// 包2:子类
package com.child;
import com.parent.Parent;
public class Child extends Parent {public void show() {System.out.println(super.name); // 合法:通过super访问(继承上下文)Parent p = new Parent(); // System.out.println(p.name); // 非法:跨包通过父类对象访问protected成员}
}3. 继承的核心规则与陷阱
(1)单继承与多层继承的设计原因
单继承
:一个类只能有一个直接父类。
✅ 原因:避免 “菱形继承冲突”—— 若类 A 同时继承 B 和 C,而 B、C 都有
method()
方法,子类调用
method()
时无法确定执行哪个父类的方法(C++ 支持多继承,需手动解决冲突,Java 简化设计)。
多层继承
:支持 “祖父→父→子” 的继承链(如
Object→Parent→Child
)。
✅ 优势:逐步细化功能,符合 “单一职责原则”(每层只扩展少量特有功能)。
(2)Object 类 —— 所有类的祖宗
底层逻辑:Java 中所有类(包括自定义类、API 类)都直接或间接继承
java.lang.Object,编译时编译器会自动为无显式父类的类添加extends Object。核心方法
(子类可重写):
toString():默认返回 “类名 @哈希码”(如com.Child@1b6d3586),重写后可返回对象属性(开发中必重写)。equals(Object obj):默认比较对象地址(==),重写后可自定义相等规则(如比较name和age)。hashCode():默认返回对象地址相关的哈希值,重写equals时必须重写(保证 “相等对象哈希值相等”)。
4. 方法重写(Override)—— 多态的前提
方法重写是子类对父类方法的 “重新实现”,需严格遵循方法签名一致性和访问权限规则,否则编译报错。
(1)重写的严格规则(编译器校验逻辑)
方法签名完全一致
:方法名、参数列表(个数、类型、顺序)必须与父类完全相同。
❌ 错误示例:父类
eat(String food)
,子类
eat(int num)
(参数类型不同,不是重写,是重载)。
访问权限不缩小
:子类重写方法的权限 ≥ 父类方法(
public > protected > 缺省
)。
❌ 错误示例:父类
public void run()
,子类
protected void run()
(权限缩小,编译报错)。
返回值类型兼容
:
JDK7 前:必须与父类完全相同。
JDK7 后:支持 “协变返回值”—— 子类返回值可是父类返回值的子类(如父类返回
Animal,子类返回Dog)。
异常声明不扩大:子类重写方法声明的异常 ≤ 父类方法(异常类型可相同或为子类)。
不可重写的方法
:
父类
private方法:子类无法继承,不存在重写。父类
static方法:属于类,子类若定义同名静态方法,是 “隐藏” 而非 “重写”(调用时看引用类型,不看对象类型)。
(2)重写的底层实现 —— 动态绑定
动态绑定
:调用重写方法时,JVM 会在
运行时根据对象的实际类型
确定执行哪个版本的方法(而非编译时根据引用类型)。
示例:
java
Parent p = new Child(); // 父类引用指向子类对象 p.eat(); // 运行时执行 Child 的 eat()(若重写),而非 Parent 的 eat()
实现原理:每个类的字节码中都有一个 “方法表”,记录方法的签名和地址。当调用方法时,JVM 先获取对象的实际类型,再从该类型的方法表中找到方法地址,执行对应逻辑。
5. 子类构造器的底层执行流程
子类构造器的核心规则:必须先初始化父类部分,再初始化子类部分,底层通过 super() 实现。
(1)默认执行逻辑
子类构造器中,若未显式调用
super()或this(),编译器会自动在构造器第一行添加super()(调用父类无参构造器)。执行顺序:父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。
(2)父类无无参构造器的解决方案
若父类自定义了有参构造器,默认无参构造器会失效,子类必须显式调用父类有参构造器:
java
// 父类(无无参构造器)
class Parent {String name;public Parent(String name) { // 自定义有参构造器this.name = name;}
}
// 子类(必须显式调用父类有参构造器)
class Child extends Parent {int age;// 方案1:调用父类有参构造器public Child(String name, int age) {super(name); // 必须在第一行,初始化父类namethis.age = age;}// 方案2:通过this()调用本类其他构造器,间接调用父类构造器public Child() {this("默认名字", 0); // 调用本类有参构造器}
}(3)this(...) 与 super(...) 的冲突
两者都必须位于构造器第一行,因此不能同时存在(JVM 需确保先初始化父类或先调用本类其他构造器,二选一)。
二、多态(OOP 三大特征之三)—— 从行为多样性到解耦
多态是 “同一行为,不同实现” 的体现,核心是父类引用指向子类对象,通过动态绑定实现灵活调用,是 Java 解耦和扩展的关键。
1. 多态的三大前提与底层条件
前提 1:继承 / 实现关系:多态基于继承(类→类)或实现(类→接口),确保父类与子类有共性行为。
前提 2:父类引用指向子类对象:
Parent p = new Child();(向上转型),引用类型是父类,对象类型是子类。前提 3:方法重写:子类重写父类的方法,否则调用的是父类的默认实现(无多态效果)。
关键注意:属性无多态—— 访问属性时,JVM 编译时根据引用类型确定,而非对象类型(如 p.name 访问的是父类的 name)。
2. 多态的核心优势 —— 解耦与扩展
(1)解耦:对象与逻辑分离
多态下,调用方仅依赖父类接口,不关心子类具体实现,修改子类时无需修改调用代码:
java
// 调用方代码(仅依赖父类)
public class Test {public static void feed(Parent p) { // 父类作为参数p.eat(); // 不关心p是Parent还是Child,统一调用}
public static void main(String[] args) {feed(new Parent()); // 传入父类对象feed(new Child()); // 传入子类对象(无需修改feed方法)}
}(2)扩展:支持动态切换实现
新增子类时,无需修改现有代码,直接传入调用方即可(符合 “开闭原则”):
java
// 新增子类
class GrandChild extends Parent {@Overridepublic void eat() {System.out.println("孙子吃饭");}
}
// 直接使用,无需修改feed方法
feed(new GrandChild()); 3. 多态的局限与类型转换解决方案
(1)局限:无法直接调用子类特有方法
父类引用仅能访问父类定义的方法,无法直接调用子类特有方法(如 p.study() 编译报错,因 Parent 无 study() 方法)。
(2)类型转换:解决子类特有方法调用
多态下的类型转换分为向上转型(自动)和向下转型(强制),底层是 “引用类型的转换”,对象实际类型不变。
| 类型转换 | 语法格式 | 特点 | 适用场景 |
|---|---|---|---|
| 向上转型(自动) | Parent p = new Child(); | 安全(子类→父类,范围扩大),无需显式转换 | 多态调用(统一接口) |
| 向下转型(强制) | Child c = (Child) p; | 风险(父类→子类,范围缩小),需确保对象实际类型是子类,否则抛 ClassCastException | 调用子类特有方法 |
(3)安全校验:instanceof 关键字
instanceof 用于判断对象的实际类型,返回 boolean,是避免类型转换异常的核心手段:
java
Parent p = new Child();
if (p instanceof Child) { // 判断p的实际类型是ChildChild c = (Child) p;c.study(); // 安全调用子类特有方法
}
if (p instanceof GrandChild) { // 实际类型不是GrandChild,返回falseGrandChild gc = (GrandChild) p; // 不会执行,避免异常
}三、核心关键字与设计模式 —— 从语法到架构
1. final 关键字 —— 不可变的底层保障
final 表示 “最终不可变”,通过编译期限制和运行时校验,确保被修饰的类、方法、变量不被修改。
(1)final 修饰类 —— 禁止继承
底层逻辑:
final类的字节码中会被标记为ACC_FINAL,编译器在处理子类继承时,若发现父类是final,直接报错。应用场景:核心工具类(如
java.lang.String、java.lang.Math),避免子类修改核心逻辑(如String的不可变性)。
(2)final 修饰方法 —— 禁止重写
底层逻辑:
final方法的字节码中标记为ACC_FINAL,子类若定义同名方法,编译器会校验方法签名,发现父类方法是final则报错。注意:
final方法仍可被继承和调用,只是不能重写。
(3)final 修饰变量 —— 值不可变
基本类型变量
:编译期直接将变量值 “嵌入” 字节码,运行时无法修改(若尝试修改,编译器报错)。
java
final int num = 10; // num = 20; // 编译报错:final变量不能修改
引用类型变量
:变量存储的
地址
不可变(无法指向新对象),但地址指向的
对象内容
可修改(如集合添加元素):
java
final List<String> list = new ArrayList<>(); list.add("a"); // 合法:对象内容可修改 // list = new LinkedList<>(); // 非法:地址不可修改
(4)常量(static final)—— 全局不可变
定义:
static final修饰的变量是 “全局常量”,具备 “类级别的不可变性”(类加载时初始化,仅一次)。命名规范:全大写字母,多单词用下划线连接(如
public static final String SCHOOL = "黑马程序员")。底层优化:编译时 “宏替换”—— 所有引用常量的地方,会直接替换为常量的字面量(如
System.out.println(SCHOOL)编译后变为System.out.println("黑马程序员")),性能与直接用字面量一致。
2. 单例设计模式 —— 确保唯一实例的底层实现
单例模式是 “创建型设计模式”,核心是确保某个类在内存中仅存在一个实例,避免重复创建对象浪费资源。
(1)饿汉式单例 —— 类加载时初始化
实现代码
:
java
public class HungrySingleton {// 1. 私有构造器:禁止外部创建对象private HungrySingleton() {}// 2. 静态成员变量:类加载时创建实例(仅一次)private static final HungrySingleton INSTANCE = new HungrySingleton();// 3. 静态方法:返回唯一实例public static HungrySingleton getInstance() {return INSTANCE;} }底层逻辑:类加载时(
HungrySingleton.class加载到方法区),静态变量INSTANCE会被初始化,由于类仅加载一次,实例也仅创建一次。优点:线程安全(类加载由 JVM 保证线程安全),简单高效。
缺点:类加载时就创建实例,若实例未被使用,会浪费内存(如工具类的单例)。
(2)懒汉式单例 —— 延迟加载(线程安全版)
问题:基础懒汉式(无锁)线程不安全(多线程同时调用
getInstance()可能创建多个实例)。线程安全实现
:双重检查锁(DCL,Double-Checked Locking)+
volatile
:
java
public class LazySingleton {private LazySingleton() {}// volatile:禁止指令重排序,确保实例初始化完成后再赋值给变量private static volatile LazySingleton instance;public static LazySingleton getInstance() {// 第一次检查:避免不必要的锁竞争(实例已创建时直接返回)if (instance == null) {synchronized (LazySingleton.class) { // 类锁:保证同一时间仅一个线程进入// 第二次检查:避免多线程等待锁时重复创建实例if (instance == null) {instance = new LazySingleton(); // 若无volatile,可能发生指令重排序:赋值给instance时,对象尚未初始化完成}}}return instance;} }关键细节:
volatile关键字的作用 —— 禁止instance = new LazySingleton()的指令重排序(该操作分三步:1. 分配内存;2. 初始化对象;3. 赋值给变量),确保其他线程看到instance非null时,对象已完全初始化。
(3)枚举单例 —— 最安全的实现
实现代码
:
java
运行
public enum EnumSingleton {INSTANCE; // 唯一实例(枚举常量)// 枚举类的方法public void doSomething() {System.out.println("枚举单例执行方法");} }底层优势
:
枚举类默认私有构造器,外部无法创建对象。
枚举类的实例由 JVM 管理,确保线程安全(枚举类加载时初始化实例)。
避免反射破坏单例(反射无法创建枚举类的实例)。
应用场景:高安全性场景(如配置中心、数据库连接池)。
3. 枚举类(Enum)—— 类型安全的常量定义
枚举是 “特殊的类”,用于定义固定数量的常量(如状态、方向),比普通常量更安全(避免非法值)。
(1)枚举类的底层结构
编译器会将枚举类编译为 “继承 java.lang.Enum 的 final 类”,枚举常量是该类的静态 final 实例:
java
// 定义枚举类
public enum Direction {UP, DOWN, LEFT, RIGHT;
}
// 编译器编译后等价于:
public final class Direction extends Enum<Direction> {public static final Direction UP = new Direction("UP", 0);public static final Direction DOWN = new Direction("DOWN", 1);public static final Direction LEFT = new Direction("LEFT", 2);public static final Direction RIGHT = new Direction("RIGHT", 3);// 私有构造器(编译器自动生成)private Direction(String name, int ordinal) {super(name, ordinal);}// 编译器自动生成的方法public static Direction[] values() { ... } // 返回所有枚举常量public static Direction valueOf(String name) { ... } // 根据名称获取常量
}(2)枚举类的核心方法
values():返回所有枚举常量的数组(开发中常用,如遍历所有状态)。valueOf(String name):根据名称获取枚举常量(名称不匹配时抛IllegalArgumentException)。name():返回枚举常量的名称(如UP.name()返回"UP")。ordinal():返回枚举常量的序号(从 0 开始,如UP.ordinal()返回0)。
(3)枚举类的扩展功能
枚举类可添加成员变量、方法,甚至实现接口:
java
public enum OrderStatus {// 枚举常量(需传入构造器参数)UNPAID(0, "未支付"),PAID(1, "已支付"),SHIPPED(2, "已发货"),DELIVERED(3, "已送达");// 枚举类的成员变量private int code;private String desc;// 私有构造器(枚举常量初始化时调用)private OrderStatus(int code, String desc) {this.code = code;this.desc = desc;}// 普通方法public String getDesc() {return desc;}// 静态方法(根据code获取枚举常量)public static OrderStatus getByCode(int code) {for (OrderStatus status : values()) {if (status.code == code) {return status;}}return null;}
}
// 使用
OrderStatus status = OrderStatus.getByCode(1);
System.out.println(status.getDesc()); // 输出:已支付四、抽象类与接口 —— 从模板到规范
1. 抽象类(abstract class)—— 不完整的类
抽象类是 “包含抽象方法的类”,仅作为父类供子类继承,强制子类实现核心行为,是 “模板方法设计模式” 的核心载体。
(1)抽象类的核心定义与规则
抽象方法:用
abstract修饰,无方法体(仅定义方法签名),必须被子类重写。抽象类定义
:
java
// 抽象类(不能创建对象) abstract class Animal {String name;// 抽象方法(无方法体)public abstract void cry();// 普通方法(有方法体,可被子类继承)public void eat() {System.out.println(name + "吃饭");} } // 子类(必须重写所有抽象方法,否则子类需为抽象类) class Dog extends Animal {@Overridepublic void cry() {System.out.println(name + "汪汪叫");} }
核心规则
:
抽象类不能创建对象(
new Animal()报错),仅用于继承。子类继承抽象类,必须重写所有抽象方法(否则子类需用
abstract修饰)。抽象类可包含普通方法、成员变量、构造器(构造器用于子类初始化父类部分)。
(2)模板方法设计模式 —— 抽象类的核心应用
核心思想:抽象类中定义 “模板方法”(封装固定流程),将可变步骤定义为抽象方法,由子类实现。
优势:固定流程不修改,可变步骤灵活扩展,提高代码复用。
示例(考试流程)
:
java
abstract class Exam {// 模板方法(final修饰,禁止子类重写,确保流程固定)public final void examProcess() {System.out.println("1. 发试卷"); // 固定步骤1doExam(); // 可变步骤(抽象方法,子类实现)System.out.println("3. 收试卷"); // 固定步骤2}// 可变步骤(抽象方法)public abstract void doExam(); } // 学生A的考试(实现可变步骤) class StudentA extends Exam {@Overridepublic void doExam() {System.out.println("2. 学生A认真做题");} } // 学生B的考试(实现可变步骤) class StudentB extends Exam {@Overridepublic void doExam() {System.out.println("2. 学生B快速做题");} } // 使用 public class Test {public static void main(String[] args) {Exam a = new StudentA();a.examProcess(); // 输出:发试卷→学生A做题→收试卷Exam b = new StudentB();b.examProcess(); // 输出:发试卷→学生B做题→收试卷} }
2. 接口(interface)—— 行为规范
接口是 “纯抽象的行为规范”,仅定义方法签名(JDK8 后支持默认方法、静态方法),用于实现 “多继承” 效果和 “面向接口编程”。
(1)接口的底层本质与语法
底层本质:接口编译后生成
class文件,本质是 “特殊的抽象类”(无构造器,所有方法默认public abstract,所有变量默认public static final)。JDK8 前接口定义
:
java
public interface Swim {// 常量(默认public static final)int MAX_SPEED = 10;// 抽象方法(默认public abstract)void swim(); }JDK8 新增方法(解决接口扩展问题)
:
默认方法(default)
:有方法体,实现类可直接调用或重写,用于接口扩展(避免修改所有实现类)。
java
interface Swim {default void warmUp() { // 默认方法System.out.println("热身运动");}void swim(); } class Fish implements Swim {@Overridepublic void swim() {System.out.println("鱼游");}// 可重写warmUp(),也可不重写(直接使用默认实现) }静态方法(static)
:有方法体,接口名直接调用,用于定义工具方法。
java
interface Swim {static void showRule() { // 静态方法System.out.println("游泳规则:禁止潜水");}void swim(); } // 使用 Swim.showRule(); // 接口名直接调用私有方法(private,JDK9+):有方法体,仅接口内部调用,用于封装默认方法或静态方法的重复逻辑。
(2)接口的核心特点
多实现
:一个类可实现多个接口(
class A implements B, C
),弥补类单继承的不足。
java
interface Run {void run(); } interface Fly {void fly(); } // 实现多个接口,需重写所有抽象方法 class Bird implements Run, Fly {@Overridepublic void run() {System.out.println("鸟跑");}@Overridepublic void fly() {System.out.println("鸟飞");} }多继承
:一个接口可继承多个接口(
interface C extends A, B
),组合多个接口的规范。
java
interface A {void methodA(); } interface B {void methodB(); } // 接口C继承A和B,包含methodA()和methodB() interface C extends A, B {void methodC(); }接口冲突解决
:
实现多个接口,若有同名默认方法:实现类必须重写该方法(否则编译报错)。
继承父类且实现接口,若父类方法与接口默认方法同名:优先使用父类方法(“类优先” 原则)。
(3)抽象类与接口的核心区别(从设计理念到底层)
| 对比维度 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 设计理念 | “is-a” 关系(子类是父类的一种,如 Dog is a Animal) | “has-a” 关系(类具备某种行为,如 Bird has a Fly) |
| 成员类型 | 可包含抽象方法、普通方法、成员变量、构造器 | JDK8 前:仅抽象方法 + 常量;JDK8 后:新增默认 / 静态 / 私有方法 |
| 继承 / 实现 | 单继承(一个类仅能继承一个抽象类) | 多实现(一个类可实现多个接口);多继承(接口可继承多个接口) |
| 访问修饰符 | 成员可多修饰符(private/protected/public 等) | 成员默认 public(常量:public static final;方法:public abstract) |
| 实例化 | 不能实例化(需子类继承后实例化) | 不能实例化(需实现类实现后实例化) |
| 底层字节码 | 标记为 abstract class,有构造器字节码 | 标记为 interface,无构造器字节码 |
| 应用场景 | 抽取类的共性(包含属性和方法),定义模板流程 | 抽取类的共性行为(仅方法),定义规范 |
五、代码块与内部类 —— 类的特殊成分
1. 代码块(初始化工具)
代码块是类的五大成分之一(成员变量、构造器、方法、代码块、内部类),用于初始化类或对象,按类型分为静态代码块和实例代码块。
(1)静态代码块(static {})
执行时机:类加载时执行(仅一次,无论创建多少对象)。
执行顺序:多个静态代码块按定义顺序执行;父类静态代码块 → 子类静态代码块。
核心作用
:
初始化静态变量(如加载配置文件、初始化全局常量)。
执行类级别的初始化逻辑(如注册驱动)。
示例
:
java
class Config {public static String DB_URL;// 静态代码块:初始化静态变量static {// 模拟加载配置文件DB_URL = "jdbc:mysql://localhost:3306/test";System.out.println("静态代码块执行:加载数据库URL");} } // 使用:访问静态变量时触发类加载,执行静态代码块 System.out.println(Config.DB_URL);
(2)实例代码块({})
执行时机:每次创建对象时执行(在构造器前执行)。
执行顺序:多个实例代码块按定义顺序执行;父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。
核心作用
:
初始化实例变量(提取多个构造器的重复初始化逻辑)。
执行对象级别的预处理逻辑(如校验初始化参数)。
示例
:
java
class Person {String name;int age;// 实例代码块:提取重复初始化逻辑{System.out.println("实例代码块执行:初始化对象");name = "默认名字"; // 所有构造器创建对象时,都会执行此初始化}// 无参构造器public Person() {System.out.println("无参构造器执行");}// 有参构造器public Person(int age) {this.age = age;System.out.println("有参构造器执行");} } // 使用:创建对象时执行实例代码块→构造器 new Person(); // 输出:实例代码块执行→无参构造器执行 new Person(18); // 输出:实例代码块执行→有参构造器执行
2. 内部类(嵌套类)—— 类的 “内部组件”
内部类是定义在另一个类(外部类)内部的类,用于封装与外部类强关联的逻辑(如外部类的 “组件” 或 “辅助逻辑”),按类型分为四种。
(1)成员内部类(非静态内部类)
定义:外部类的普通成员(无
static修饰),与外部类的实例绑定(需通过外部类实例创建)。创建对象语法:
外部类名.内部类名 变量 = new 外部类().new 内部类();核心特点
:
内部类持有外部类的引用(
外部类名.this),可直接访问外部类的所有成员(包括private成员)。外部类访问内部类成员,需通过内部类实例。
示例
:
java
class Outer {private String outerName = "外部类名字";// 成员内部类class Inner {String innerName = "内部类名字";public void show() {// 直接访问外部类private成员System.out.println("外部类名字:" + outerName);System.out.println("内部类名字:" + innerName);// 通过Outer.this获取外部类实例System.out.println("外部类实例:" + Outer.this);}}// 外部类访问内部类成员public void accessInner() {Inner inner = new Inner();System.out.println("内部类名字:" + inner.innerName);} } // 使用 public class Test {public static void main(String[] args) {// 创建内部类对象:需先创建外部类实例Outer.Inner inner = new Outer().new Inner();inner.show();// 外部类访问内部类Outer outer = new Outer();outer.accessInner();} }底层原理:编译器会为成员内部类生成构造器,传入外部类实例(如
Inner(Outer outer) { this.outer = outer; }),内部类通过该引用访问外部类成员。
(2)静态内部类(static 内部类)
定义:用
static修饰的内部类,与外部类的类本身绑定(无需外部类实例,直接通过外部类名创建)。创建对象语法:
外部类名.内部类名 变量 = new 外部类名.内部类名();核心特点
:
不持有外部类引用,仅可直接访问外部类的静态成员(无法访问实例成员,需通过外部类实例)。
可独立存在(类似普通类,只是定义在外部类内部)。
示例
:
java
class Outer {private static String staticName = "外部类静态名字";private String instanceName = "外部类实例名字";// 静态内部类static class Inner {public void show() {// 直接访问外部类静态成员System.out.println("外部类静态名字:" + staticName);// 无法直接访问外部类实例成员(需外部类实例)// System.out.println(instanceName); // 编译报错// 通过外部类实例访问实例成员Outer outer = new Outer();System.out.println("外部类实例名字:" + outer.instanceName);}} } // 使用:无需外部类实例,直接创建内部类对象 Outer.Inner inner = new Outer.Inner(); inner.show();应用场景:外部类的 “工具组件”(如
java.util.HashMap的Entry内部类,用于存储键值对)。
(3)局部内部类(方法内的内部类)
定义:定义在方法、代码块、构造器等局部范围内的内部类,作用域仅限局部范围。
核心特点
:
访问局部变量时,局部变量必须是
final
修饰(JDK8 后隐式
,即变量值不可修改)。
✅ 原因:局部变量的生命周期与内部类对象生命周期不一致 —— 局部变量随方法执行结束销毁,而内部类对象可能仍存在(如被外部引用),
final
确保变量值不被修改,避免内部类访问到 “已销毁的变量”。
实用性低(仅在局部范围使用,无法外部访问),开发中极少使用。
示例
:
java
class Outer {public void method() {final int num = 10; // 局部变量,隐式final(JDK8+)// 局部内部类(仅在method()中可见)class Inner {public void show() {System.out.println(num); // 合法:访问final局部变量}}// 局部范围内创建内部类对象并使用Inner inner = new Inner();inner.show();} }
(4)匿名内部类(无名称的局部内部类)
定义:无类名的局部内部类,本质是 “继承父类 / 实现接口的子类匿名对象”,用于快速创建一次性使用的子类对象。
语法格式
:
java
new 父类/接口(构造参数) {// 类体:重写父类方法或实现接口方法@Overridepublic void 方法名() {// 方法实现} };核心特点
:
无类名,编译器自动生成类名(如
Outer$1.class)。本质是子类对象,创建后立即使用(仅一次)。
仅能继承一个父类或实现一个接口(不能同时)。
开发中的核心应用场景
:
作为方法参数传递(简化代码,替代显式子类)。
java
// 接口 interface Swim {void swim(); } // 方法:参数为Swim接口 public static void testSwim(Swim s) {s.swim(); } // 使用匿名内部类作为参数 testSwim(new Swim() {@Overridepublic void swim() {System.out.println("匿名内部类实现游泳");} });事件监听(如 Swing 按钮点击、集合排序)。
java
// 集合排序:匿名内部类实现Comparator接口 List<String> list = Arrays.asList("b", "a", "c"); Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.compareTo(o2); // 按自然顺序排序} });
底层原理:编译器生成匿名类文件(如
Test$1.class),该类继承父类或实现接口,并重写方法;运行时创建该匿名类的实例,传递给方法或直接使用。
六、函数式编程(JDK8+)—— 从面向对象到面向函数
函数式编程的核心是 “关注做什么,而非怎么做”,通过 Lambda 表达式 和 方法引用 简化代码,基于 “函数式接口” 实现。
1. 函数式接口 —— 函数式编程的基石
定义:有且仅有一个抽象方法的接口,用
@FunctionalInterface注解校验(可选,但推荐添加,编译器会强制检查是否为函数式接口)。核心作用:作为 Lambda 表达式的 “类型载体”(Lambda 表达式本质是函数式接口的匿名实现)。
常见内置函数式接口
(
java.util.function
包):
接口名 抽象方法 功能描述 Predicateboolean test(T t)判断输入参数是否满足条件 Consumervoid accept(T t)消费输入参数(无返回值) FunctionR apply(T t)将输入参数转换为另一种类型 SupplierT get()提供一个数据(无输入,有返回) 自定义函数式接口示例
:
java
// 自定义函数式接口(仅一个抽象方法) @FunctionalInterface interface Calculator {int calculate(int a, int b); // 抽象方法:计算两个数 }
2. Lambda 表达式 —— 函数式接口的简化写法
Lambda 是函数式接口的 “匿名内部类简化版”,通过简洁的语法替代冗余的匿名内部类代码。
(1)Lambda 的语法格式
完整格式
:
(参数列表) -> { 方法体; }(参数列表):对应函数式接口抽象方法的参数(类型可省略,编译器自动推断)。->:Lambda 运算符,分隔参数列表和方法体。{ 方法体; }:对应抽象方法的实现(若仅一行代码,{}和;可省略;若有返回值,return也可省略)。
(2)Lambda 的简化规则(核心)
参数类型可省略:编译器根据函数式接口的抽象方法自动推断参数类型。
参数括号可省略:若仅一个参数,
()可省略;多个参数不可省略。方法体大括号可省略:若方法体仅一行代码,
{}和;可省略;若有return,需一并省略。
(3)示例演变(从匿名内部类到 Lambda)
以 Calculator 接口为例:
java
// 1. 匿名内部类实现
Calculator add1 = new Calculator() {@Overridepublic int calculate(int a, int b) {return a + b;}
};
// 2. Lambda 完整写法
Calculator add2 = (int a, int b) -> {return a + b;
};
// 3. Lambda 简化写法(省略参数类型、大括号、return)
Calculator add3 = (a, b) -> a + b;
// 使用
System.out.println(add3.calculate(10, 20)); // 输出:30(4)Lambda 的底层实现
JDK8 前:Lambda 会被编译为匿名内部类(生成
Test$1.class),与匿名内部类实现一致。JDK8 后:引入
invokedynamic指令,直接动态生成 Lambda 函数的调用逻辑,避免创建匿名类文件,性能更优。
3. 方法引用 ——Lambda 的进一步简化
方法引用是 “Lambda 表达式的语法糖”,当 Lambda 逻辑仅调用一个已存在的方法时,可直接引用该方法,替代重复的 Lambda 代码。
(1)方法引用的四种形式
???
🍐 遇到知识盲区了
| 引用形式 | 语法格式 | 适用场景 | 示例 |
|---|---|---|---|
| 静态方法引用 | 类名::静态方法名 | Lambda 逻辑仅调用某个静态方法,且参数列表与静态方法一致 | Calculator add = Integer::sum;(Integer.sum(a,b) 与 calculate(a,b) 参数一致) |
| 实例方法引用 | 对象名::实例方法名 | Lambda 逻辑仅调用某个对象的实例方法,且参数列表与实例方法一致 | String str = "hello"; Predicate isEqual = str::equals;(str.equals(s) 与 test(s) 参数一致) |
| 特定类型方法引用 | 类名::实例方法名 | Lambda 第一个参数是方法的调用者,后续参数是方法的参数 | Comparator comp = String::compareTo;(s1.compareTo(s2) 对应 compare(s1,s2)) |
| 构造器引用 | 类名::new | Lambda 逻辑仅创建对象,且参数列表与构造器一致 | Supplier> listSup = ArrayList::new;(new ArrayList<>() 与 get() 无参数一致) |
(2)示例:方法引用简化集合排序
java
List<String> list = Arrays.asList("b", "a", "c");
// 1. 匿名内部类排序
Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.compareTo(o2);}
});
// 2. Lambda 简化
Collections.sort(list, (o1, o2) -> o1.compareTo(o2));
// 3. 方法引用(特定类型方法引用)
Collections.sort(list, String::compareTo);七、常用 API 与 GUI 编程 —— 从理论到实践
1. String 类 —— 不可变字符串的底层实现
String 类封装字符串数据,是 不可变类(对象创建后,字符内容无法修改),底层基于字符数组(JDK9 后改为字节数组)实现。
(1)String 的不可变性底层原理
JDK8 及之前:底层是
private final char[] value(final修饰数组,地址不可变;private修饰,外部无法修改数组元素)。JDK9 及之后:底层是
private final byte[] value+private final byte coder(编码标识),节省内存(如 Latin-1 编码的字符串,1 个字符占 1 字节,而 char 占 2 字节)。不可变性的体现:任何修改 String 的操作(如
substring、replace)都会创建新的 String 对象,原对象不变。
(2)String 常量池 —— 避免重复创建对象
定义:方法区中的一块特殊区域,存储字符串常量(如
"abc"),避免重复创建相同内容的 String 对象。核心机制
:
直接赋值创建 String(
String s = "abc"):先检查常量池,若存在"abc",直接返回引用;若不存在,创建常量并放入常量池。new创建 String(String s = new String("abc")):先检查常量池,若不存在"abc"则创建;再在堆内存创建 String 对象,引用指向堆对象。
intern()方法:将堆中的 String 对象加入常量池(若常量池无该对象),返回常量池中的引用:
java
String s1 = new String("abc"); // 堆对象:s1指向堆 String s2 = s1.intern(); // s2指向常量池的"abc" String s3 = "abc"; // s3指向常量池的"abc" System.out.println(s2 == s3); // true:都指向常量池
(3)String 常用方法(核心)
| 方法名 | 功能描述 |
|---|---|
int length() | 获取字符串长度(字符个数) |
char charAt(int index) | 获取指定索引的字符(索引从 0 开始) |
boolean equals(Object obj) | 比较字符串内容(区分大小写) |
boolean equalsIgnoreCase(String s) | 比较字符串内容(忽略大小写) |
String substring(int begin) | 从 begin 索引截取到末尾,返回新字符串 |
String substring(int begin, int end) | 从 begin 截取到 end-1(包前不包后),返回新字符串 |
String replace(CharSequence old, CharSequence new) | 替换字符串中的旧字符 / 子串,返回新字符串 |
String[] split(String regex) | 按 regex 分割字符串,返回字符串数组(regex 是正则表达式,如 split("\\.") 分割点) |
boolean contains(CharSequence s) | 判断字符串是否包含子串 s |
2. ArrayList 集合 —— 动态数组的底层实现
ArrayList 是 List 接口的实现类,基于 动态数组 实现,支持动态扩容,是开发中最常用的集合。
(1)ArrayList 的底层结构
底层存储:
transient Object[] elementData(transient修饰,序列化时仅序列化实际元素,而非整个数组)。容量与大小
:
容量(Capacity):
elementData数组的长度(默认初始容量 10)。大小(Size):集合中实际元素的个数(
size变量记录)。
(2)ArrayList 的扩容机制
当添加元素时,若 size == 容量,触发扩容:
扩容公式:新容量 = 旧容量 + (旧容量>> 1)(即旧容量的 1.5 倍,如 10→15→22...)。
扩容过程
:
创建新容量的数组(
Arrays.copyOf(elementData, newCapacity))。将原数组元素复制到新数组。
elementData指向新数组。
注意:扩容是 “复制数组” 的过程,频繁扩容会影响性能,因此可通过
new ArrayList(int initialCapacity)指定初始容量(如已知存储 1000 个元素,初始容量设为 1000,避免扩容)。
(3)ArrayList 常用方法(核心)
| 方法名 | 功能描述 |
|---|---|
boolean add(E e) | 尾部添加元素,返回是否成功 |
void add(int index, E e) | 在 index 位置插入元素,后续元素后移 |
E get(int index) | 获取 index 位置的元素 |
E set(int index, E e) | 修改 index 位置的元素为 e,返回旧元素 |
E remove(int index) | 删除 index 位置的元素,后续元素前移,返回被删除元素 |
boolean remove(Object o) | 删除第一个等于 o 的元素,返回是否成功 |
int size() | 获取集合中元素的个数 |
boolean isEmpty() | 判断集合是否为空 |
void clear() | 清空集合中的所有元素 |
3. GUI 编程(Swing)—— 图形化界面开发
Swing 是 Java 提供的 GUI 组件库,基于 AWT 开发,是 轻量级组件(不依赖操作系统的本地组件,跨平台性好)。
(1)Swing 核心组件
| 组件类名 | 功能描述 |
|---|---|
JFrame | 顶层窗口组件(必须,每个 GUI 程序至少一个) |
JPanel | 面板组件(用于组织其他组件,如按钮、输入框) |
JButton | 按钮组件(触发点击事件) |
JTextField | 单行输入框组件(接收用户输入的文本) |
JPasswordField | 密码输入框组件(输入内容隐藏为星号) |
JTable | 表格组件(展示多行多列数据) |
JLabel | 标签组件(显示文本或图片,不可编辑) |
(2)Swing 布局管理器
布局管理器用于自动排列组件,避免手动设置组件的位置和大小(不同操作系统分辨率不同,手动设置易错位)。
| 布局管理器类名 | 布局特点 | 适用场景 |
|---|---|---|
FlowLayout | 流式布局:组件按顺序排列,一行排满换行 | 简单组件排列(如按钮组) |
BorderLayout | 边界布局:将容器分为东(EAST)、南(SOUTH)、西(WEST)、北(NORTH)、中(CENTER)五个区域,每个区域仅一个组件 | 顶层窗口布局(如 JFrame) |
GridLayout | 网格布局:将容器分为多行多列的网格,组件按行填充 | 规则排列组件(如登录表单) |
BoxLayout | 箱式布局:组件按水平或垂直方向排列,可设置间距 | 精确控制组件排列方向 |
(3)Swing 事件处理(观察者模式)
核心机制:事件源(如按钮)→ 事件(如点击)→ 事件监听器(如
ActionListener),监听器监听事件源的事件,触发时执行处理逻辑。常用事件监听器
:
ActionListener:处理点击事件(如按钮点击)。MouseListener:处理鼠标事件(如鼠标点击、移入、移出)。KeyListener:处理键盘事件(如按键按下、释放)。
示例:按钮点击事件
java
import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class SwingDemo {public static void main(String[] args) {// 1. 创建顶层窗口JFrame frame = new JFrame("Swing示例");frame.setSize(400, 300); // 窗口大小frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口时退出程序frame.setLocationRelativeTo(null); // 窗口居中 // 2. 创建面板和按钮JPanel panel = new JPanel();JButton btn = new JButton("点击我"); // 3. 绑定点击事件监听器(匿名内部类)btn.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {// 事件处理逻辑:弹出对话框JOptionPane.showMessageDialog(frame, "按钮被点击了!");}}); // 4. 组装组件panel.add(btn);frame.add(panel); // 5. 显示窗口frame.setVisible(true);} }基本是用不到了,除非是学校小组作业👍
八、综合案例:员工信息管理系统(完整实现)
1. 需求设计
功能:员工信息的添加、查询、修改、删除,表格展示,搜索功能。
技术栈:Swing(界面)+ ArrayList(数据存储)+ 接口(功能规范)。
2. 核心代码实现
(1)员工实体类(Employee.java)
java
public class Employee {private int id;private String name;private String job;private String dept;
// 构造器public Employee() {}public Employee(int id, String name, String job, String dept) {this.id = id;this.name = name;this.job = job;this.dept = dept;}
// getter/setterpublic int getId() { return id; }public void setId(int id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public String getJob() { return job; }public void setJob(String job) { this.job = job; }public String getDept() { return dept; }public void setDept(String dept) { this.dept = dept; }
// toString(便于调试)@Overridepublic String toString() {return "Employee{" +"id=" + id +", name='" + name + '\'' +", job='" + job + '\'' +", dept='" + dept + '\'' +'}';}
}(2)数据访问接口(EmployeeDAO.java)
java
import java.util.List;
public interface EmployeeDAO {void add(Employee emp); // 添加员工List<Employee> queryAll(); // 查询所有员工Employee queryById(int id); // 按ID查询员工boolean update(Employee emp); // 修改员工boolean delete(int id); // 按ID删除员工List<Employee> search(String keyword); // 按关键词搜索(姓名/部门)
}(3)数据访问实现类(EmployeeDAOImpl.java)
java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class EmployeeDAOImpl implements EmployeeDAO {// 模拟数据库:ArrayList存储员工数据private List<Employee> empList = new ArrayList<>();// 自增ID(模拟主键)private int nextId = 1;
// 初始化测试数据public EmployeeDAOImpl() {empList.add(new Employee(nextId++, "张三", "开发工程师", "技术部"));empList.add(new Employee(nextId++, "李四", "产品经理", "产品部"));empList.add(new Employee(nextId++, "王五", "测试工程师", "测试部"));}
@Overridepublic void add(Employee emp) {emp.setId(nextId++); // 分配自增IDempList.add(emp);}
@Overridepublic List<Employee> queryAll() {return new ArrayList<>(empList); // 返回副本,避免外部修改原集合}
@Overridepublic Employee queryById(int id) {for (Employee emp : empList) {if (emp.getId() == id) {return emp;}}return null;}
@Overridepublic boolean update(Employee emp) {Employee oldEmp = queryById(emp.getId());if (oldEmp != null) {oldEmp.setName(emp.getName());oldEmp.setJob(emp.getJob());oldEmp.setDept(emp.getDept());return true;}return false;}
@Overridepublic boolean delete(int id) {Employee emp = queryById(id);if (emp != null) {empList.remove(emp);return true;}return false;}
@Overridepublic List<Employee> search(String keyword) {// 按姓名或部门包含关键词搜索(忽略大小写)return empList.stream().filter(emp -> emp.getName().toLowerCase().contains(keyword.toLowerCase())|| emp.getDept().toLowerCase().contains(keyword.toLowerCase())).collect(Collectors.toList());}
}(4)GUI 窗口类(EmployeeFrame.java)
java
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
public class EmployeeFrame extends JFrame {private EmployeeDAO empDAO = new EmployeeDAOImpl();private JTable table;private DefaultTableModel tableModel;private JTextField searchField;
public EmployeeFrame() {// 1. 初始化窗口setTitle("员工信息管理系统");setSize(800, 600);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setLocationRelativeTo(null); // 居中setLayout(new BorderLayout());
// 2. 创建顶部面板(搜索+添加按钮)JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));searchField = new JTextField(20);JButton searchBtn = new JButton("搜索");JButton addBtn = new JButton("添加");
// 搜索按钮事件searchBtn.addActionListener(e -> searchEmployee());// 添加按钮事件addBtn.addActionListener(e -> addEmployee());
topPanel.add(new JLabel("关键词:"));topPanel.add(searchField);topPanel.add(searchBtn);topPanel.add(addBtn);add(topPanel, BorderLayout.NORTH);
// 3. 创建表格(展示员工数据)String[] columnNames = {"ID", "姓名", "职位", "部门"};tableModel = new DefaultTableModel(columnNames, 0);table = new JTable(tableModel);JScrollPane scrollPane = new JScrollPane(table);add(scrollPane, BorderLayout.CENTER);
// 4. 创建底部面板(修改+删除按钮)JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));JButton updateBtn = new JButton("修改");JButton deleteBtn = new JButton("删除");
// 修改按钮事件updateBtn.addActionListener(e -> updateEmployee());// 删除按钮事件deleteBtn.addActionListener(e -> deleteEmployee());
bottomPanel.add(updateBtn);bottomPanel.add(deleteBtn);add(bottomPanel, BorderLayout.SOUTH);
// 5. 加载员工数据loadEmployeeData(empDAO.queryAll());
// 6. 显示窗口setVisible(true);}
// 加载员工数据到表格private void loadEmployeeData(List<Employee> empList) {// 清空表格tableModel.setRowCount(0);// 添加数据for (Employee emp : empList) {Object[] rowData = {emp.getId(),emp.getName(),emp.getJob(),emp.getDept()};tableModel.addRow(rowData);}}
// 搜索员工private void searchEmployee() {String keyword = searchField.getText().trim();List<Employee> result = empDAO.search(keyword);loadEmployeeData(result);}
// 添加员工private void addEmployee() {// 创建添加表单JTextField nameField = new JTextField(10);JTextField jobField = new JTextField(10);JTextField deptField = new JTextField(10);
JPanel panel = new JPanel(new GridLayout(3, 2, 5, 5));panel.add(new JLabel("姓名:"));panel.add(nameField);panel.add(new JLabel("职位:"));panel.add(jobField);panel.add(new JLabel("部门:"));panel.add(deptField);
// 弹出对话框int option = JOptionPane.showConfirmDialog(this, panel, "添加员工", JOptionPane.OK_CANCEL_OPTION);if (option == JOptionPane.OK_OPTION) {// 获取输入内容String name = nameField.getText().trim();String job = jobField.getText().trim();String dept = deptField.getText().trim();
// 简单校验if (name.isEmpty() || job.isEmpty() || dept.isEmpty()) {JOptionPane.showMessageDialog(this, "请填写完整信息!");return;}
// 添加员工empDAO.add(new Employee(0, name, job, dept));// 重新加载数据loadEmployeeData(empDAO.queryAll());JOptionPane.showMessageDialog(this, "添加成功!");}}
// 修改员工private void updateEmployee() {// 获取选中行int selectedRow = table.getSelectedRow();if (selectedRow == -1) {JOptionPane.showMessageDialog(this, "请选中要修改的员工!");return;}
// 获取选中员工的IDint id = (int) tableModel.getValueAt(selectedRow, 0);Employee emp = empDAO.queryById(id);if (emp == null) {JOptionPane.showMessageDialog(this, "员工不存在!");return;}
// 创建修改表单(预填现有信息)JTextField nameField = new JTextField(emp.getName(), 10);JTextField jobField = new JTextField(emp.getJob(), 10);JTextField deptField = new JTextField(emp.getDept(), 10);
JPanel panel = new JPanel(new GridLayout(3, 2, 5, 5));panel.add(new JLabel("姓名:"));panel.add(nameField);panel.add(new JLabel("职位:"));panel.add(jobField);panel.add(new JLabel("部门:"));panel.add(deptField);
// 弹出对话框int option = JOptionPane.showConfirmDialog(this, panel, "修改员工", JOptionPane.OK_CANCEL_OPTION);if (option == JOptionPane.OK_OPTION) {// 获取修改后的内容String name = nameField.getText().trim();String job = jobField.getText().trim();String dept = deptField.getText().trim();
// 简单校验if (name.isEmpty() || job.isEmpty() || dept.isEmpty()) {JOptionPane.showMessageDialog(this, "请填写完整信息!");return;}
// 修改员工emp.setName(name);emp.setJob(job);emp.setDept(dept);boolean success = empDAO.update(emp);if (success) {// 重新加载数据loadEmployeeData(empDAO.queryAll());JOptionPane.showMessageDialog(this, "修改成功!");} else {JOptionPane.showMessageDialog(this, "修改失败!");}}}
// 删除员工private void deleteEmployee() {// 获取选中行int selectedRow = table.getSelectedRow();if (selectedRow == -1···🍐 up也是遇到好多不熟悉的 尤其是lambda表达式方面 🥹
Java OOP 高级 15 道难度递增面试题(含场景 + 答案)
面试题 1(基础:继承 + 方法重写)
场景
面试官:“以下代码定义了Person父类和Student子类,试图重写toString()方法展示学生信息,请分析代码是否存在问题,若有请修复,并解释方法重写的核心规则。”
java
class Person {protected String name;protected int age;
public Person(String name, int age) {this.name = name;this.age = age;}
public String toString() {return "姓名:" + name + ",年龄:" + age;}
}
class Student extends Person {private String studentId;
public Student(String name, int age, String studentId) {this.name = name;this.age = age;this.studentId = studentId;}
// 试图重写toString方法public String toString(String prefix) {return prefix + " 姓名:" + name + ",年龄:" + age + ",学号:" + studentId;}
}问题
代码存在什么问题?请修复并写出完整正确代码。
方法重写(Override)的核心规则有哪些?
子类重写父类方法时,访问权限修饰符有什么要求?
面试题 2(基础进阶:子类构造器规则)
场景
面试官:“以下代码试图通过继承实现Teacher类,编译时出现错误,请分析错误原因并修复,同时解释子类构造器的核心规则。”
java
class People {private String name;private int age;
// 父类仅有有参构造器,无无参构造器public People(String name, int age) {this.name = name;this.age = age;}
// getter/setter方法public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
}
class Teacher extends People {private String subject;
// 子类构造器public Teacher(String name, int age, String subject) {this.subject = subject;}
public void teach() {System.out.println(name + "老师教" + subject);}
}问题
代码编译错误的原因是什么?
修复代码使其能正常运行,写出完整修复代码。
子类构造器中
this(...)和super(...)能否同时存在?为什么?
面试题 3(中级:多态的前提与表现)
场景
面试官:“以下代码尝试体现多态特性,请分析运行结果并解释原因,同时说明多态的实现前提。”
java
class Animal {public String name = "动物";
public void cry() {System.out.println("动物发出叫声");}
}
class Dog extends Animal {public String name = "小狗";
@Overridepublic void cry() {System.out.println("小狗汪汪叫");}
}
public class PolymorphismDemo {public static void main(String[] args) {Animal animal = new Dog();System.out.println(animal.name);animal.cry();}
}问题
代码的运行结果是什么?
为什么会出现该结果?多态下属性和方法的访问规则有何不同?
多态的实现前提有哪些?
面试题 4(中级:多态下的类型转换)
场景
面试官:“以下代码试图通过多态实现学生和老师的信息展示,需要调用子类特有方法,请修复代码中的错误,确保能正确调用study()和teach()方法,并解释类型转换的核心规则。”
java
class People {protected String name;
public People(String name) {this.name = name;}
public void show() {System.out.println("姓名:" + name);}
}
class Student extends People {public Student(String name) {super(name);}
public void study() {System.out.println(name + "学生正在学习");}
}
class Teacher extends People {public Teacher(String name) {super(name);}
public void teach() {System.out.println(name + "老师正在授课");}
}
public class CastDemo {public static void main(String[] args) {People p1 = new Student("张三");People p2 = new Teacher("李四");
p1.study(); // 报错p2.teach(); // 报错}
}问题
代码报错的原因是什么?
修复代码使其能正确调用子类特有方法,写出完整修复代码。
强制类型转换前为什么需要用
instanceof判断?如何使用?
面试题 5(中级:final 关键字的用法)
场景
面试官:“final关键字是 Java 中的重要修饰符,请分析以下代码的运行结果,若有错误请说明原因,并详细解释final修饰类、方法、变量的不同作用。”
java
final class FinalClass {public final String INFO = "常量";public static final int MAX = 100;
public final void show() {System.out.println("FinalClass的show方法");}
}
// 试图继承FinalClass
class SubClass extends FinalClass {@Overridepublic void show() {System.out.println("子类重写的show方法");}
}
public class FinalDemo {public static void main(String[] args) {FinalClass fc = new FinalClass();fc.INFO = "修改常量"; // 试图修改final变量System.out.println(fc.MAX);}
}问题
代码存在哪些错误?分别说明原因。
final修饰类、方法、变量的核心作用是什么?static final修饰的常量与普通final变量有何区别?
面试题 6(中级:抽象类的核心规则)
场景
面试官:“请基于抽象类设计一个Shape(图形)体系,包含Circle(圆形)和Rectangle(矩形),要求:1. Shape为抽象类,包含抽象方法calculateArea()(计算面积)和普通方法show()(展示图形类型);2. 子类必须实现calculateArea()方法;3. 测试类通过多态创建对象并计算面积。”
问题
写出完整的代码实现(包含
Shape、Circle、Rectangle和测试类)。抽象类能否创建对象?为什么?
子类继承抽象类后,若不重写所有抽象方法,会有什么后果?
面试题 7(中级:接口的特性与 JDK8 新特性)
场景
面试官:“以下代码定义了Flyable接口和Bird实现类,请分析代码是否能正常运行,若有问题请修复,并解释接口的核心特性及 JDK8 后接口的新增方法类型。”
java
interface Flyable {String TYPE = "飞行生物"; // 常量
// 抽象方法void fly();
// JDK8新增默认方法default void showType() {System.out.println("类型:" + TYPE);}
// JDK8新增静态方法static void showRule() {System.out.println("飞行规则:遵守空中交通");}
}
class Bird implements Flyable {private String name;
public Bird(String name) {this.name = name;}
// 实现fly方法public void fly() {System.out.println(name + "展翅高飞");}
// 重写默认方法public void showType() {System.out.println("自定义类型:" + TYPE);}
}
public class InterfaceDemo {public static void main(String[] args) {Flyable bird = new Bird("雄鹰");bird.fly();bird.showType();Flyable.showRule();System.out.println(bird.TYPE);}
}问题
代码能否正常运行?若能,运行结果是什么?若不能,修复错误。
接口的核心特性有哪些?(至少 3 点)
JDK8 后接口新增了哪几种方法?各自的访问规则是什么?
面试题 8(中高:抽象类 vs 接口的区别与选择)
场景
面试官:“某电商平台需要设计支付功能,支持微信支付、支付宝支付、银联支付,每种支付方式都有‘发起支付’和‘查询支付状态’两个核心功能,同时所有支付方式都需要遵守‘支付安全校验’的固定流程。请回答以下问题:”
问题
该场景适合用抽象类还是接口实现?为什么?
抽象类和接口的核心区别有哪些?(从继承关系、成员类型、设计理念 3 个维度说明)
写出核心代码框架(包含抽象类 / 接口和任意两个支付实现类)。
面试题 9(中高:单例设计模式)
场景
面试官:“单例模式确保类只能创建一个对象,请分析以下饿汉式和懒汉式单例代码的优缺点,修复懒汉式的线程安全问题,并解释单例模式的应用场景。”
java
// 饿汉式单例
class HungrySingleton {private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton getInstance() {return INSTANCE;}
}
// 懒汉式单例(存在线程安全问题)
class LazySingleton {private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}问题
饿汉式单例的优缺点是什么?
懒汉式单例存在什么问题?如何修复(写出线程安全的懒汉式代码)?
单例模式的应用场景有哪些?为什么这些场景需要单例?
面试题 10(中高:枚举类的特性与应用)
场景
面试官:“请设计一个枚举类OrderStatus,表示订单的状态(待支付、已支付、已发货、已完成、已取消),要求:1. 每个状态包含对应的中文描述;2. 提供方法根据状态名称获取中文描述;3. 测试类遍历所有状态并打印。”
问题
写出完整的
OrderStatus枚举类和测试类代码。枚举类的核心特性有哪些?(至少 3 点)
枚举类与普通类相比,有哪些优势?
面试题 11(中高:代码块的执行顺序)
场景
面试官:“以下代码包含静态代码块、实例代码块和构造器,请分析运行结果并详细解释代码块的执行顺序规则。”
java
class BlockDemo {// 静态代码块static {System.out.println("静态代码块1");}
// 实例代码块{System.out.println("实例代码块1");}
public BlockDemo() {System.out.println("无参构造器");}
// 静态代码块2static {System.out.println("静态代码块2");}
// 实例代码块2{System.out.println("实例代码块2");}
}
public class BlockOrderDemo {public static void main(String[] args) {new BlockDemo();System.out.println("--------");new BlockDemo();}
}问题
代码的运行结果是什么?
静态代码块和实例代码块的核心区别是什么?(执行时机、执行次数、访问权限)
代码块、构造器、父类初始化的整体执行顺序是什么?
面试题 12(高级:成员内部类的访问规则)
场景
面试官:“以下代码定义了外部类Outer和成员内部类Inner,试图实现内外类的相互访问,请分析代码是否存在问题,若有请修复,并解释成员内部类的访问规则。”
java
class Outer {private String outerName = "外部类名称";static String outerStatic = "外部静态变量";
class Inner {private String innerName = "内部类名称";
public void showInner() {System.out.println(innerName);System.out.println(outerName);System.out.println(outerStatic);}}
public void showOuter() {System.out.println(outerName);System.out.println(innerName); // 报错System.out.println(outerStatic);}
}
public class InnerClassDemo {public static void main(String[] args) {// 试图创建内部类对象Inner inner = new Inner(); // 报错inner.showInner();}
}问题
代码存在哪些错误?分别说明原因。
修复代码使其能正常运行,写出完整修复代码。
成员内部类的创建方式是什么?内外类相互访问成员的规则是什么?
面试题 13(高级:匿名内部类与 Lambda 转换)
场景
面试官:“以下代码通过匿名内部类实现Comparator接口完成数组排序,请将其改写为 Lambda 表达式,并解释 Lambda 表达式的核心语法和省略规则。”
java
import java.util.Arrays;
import java.util.Comparator;
class Student {private String name;private int age;
public Student(String name, int age) {this.name = name;this.age = age;}
// getter方法public String getName() { return name; }public int getAge() { return age; }
@Overridepublic String toString() {return "姓名:" + name + ",年龄:" + age;}
}
public class AnonymousLambdaDemo {public static void main(String[] args) {Student[] students = {new Student("张三", 20),new Student("李四", 18),new Student("王五", 22)};
// 匿名内部类实现Comparator接口,按年龄升序排序Arrays.sort(students, new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {return s1.getAge() - s2.getAge();}});
System.out.println(Arrays.toString(students));}
}问题
将匿名内部类改写为 Lambda 表达式,写出完整的
main方法代码。Lambda 表达式的核心语法是什么?有哪些省略规则?
Lambda 表达式的使用前提是什么?
面试题 14(高级:方法引用的应用)
场景
面试官:“以下代码通过 Lambda 表达式实现了字符串长度比较和学生对象创建,请将其改写为方法引用形式,并解释每种方法引用的适用场景。”
java
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;
class Person {private String name;private int age;
public Person() {}
public Person(String name, int age) {this.name = name;this.age = age;}
@Overridepublic String toString() {return "姓名:" + name + ",年龄:" + age;}
}
public class MethodReferenceDemo {public static void main(String[] args) {// 1. Lambda:字符串按长度比较Comparator<String> comp1 = (s1, s2) -> s1.length() - s2.length();String[] strs = {"apple", "banana", "orange"};Arrays.sort(strs, comp1);System.out.println(Arrays.toString(strs));
// 2. Lambda:创建Person对象Supplier<Person> sup1 = () -> new Person();Person p = sup1.get();System.out.println(p);}
}问题
将两处 Lambda 表达式改写为方法引用,写出完整代码。
方法引用有哪几种形式?各自的适用场景是什么?
方法引用与 Lambda 表达式的关系是什么?
面试题 15(高级:综合应用:接口 + 多态 + 集合)
场景
面试官:“请设计一个简易的商品管理系统,要求:1. 定义Product接口,包含getInfo()(获取商品信息)和getPrice()(获取价格)方法;2. 实现Electronics(电子产品)和Clothing(服装)两个类,分别实现Product接口;3. 定义ProductManager类,包含addProduct()(添加商品)、queryAll()(查询所有商品)、queryByPriceRange()(按价格区间查询商品)方法;4. 测试类完成商品添加、查询功能。”
问题
写出完整的代码实现(包含接口、实现类、管理类、测试类)。
该设计中体现了 OOP 的哪些核心特性?请具体说明。
若需新增 “食品” 商品类型,该设计是否具备扩展性?为什么?
参考答案
面试题 1 参考答案
1. 问题与修复
问题:
Student类的toString(String prefix)方法不是重写,而是重载(参数列表不同),违反方法重写的 “参数列表必须与父类一致” 规则;子类构造器未调用父类构造器(父类无无参构造器)。修复后代码:
java
class Person {protected String name;protected int age;
public Person(String name, int age) {this.name = name;this.age = age;}
public String toString() {return "姓名:" + name + ",年龄:" + age;}
}
class Student extends Person {private String studentId;
// 调用父类有参构造器public Student(String name, int age, String studentId) {super(name, age); // 显式调用父类构造器this.studentId = studentId;}
// 正确重写toString方法(参数列表与父类一致)@Overridepublic String toString() {return "学生信息:" + super.toString() + ",学号:" + studentId;}
}2. 方法重写核心规则
方法名、参数列表必须与父类完全一致;
返回值类型:与父类一致或为其子类(协变返回值);
访问权限:子类方法权限 ≥ 父类方法权限;
私有方法、静态方法不能重写;
可添加
@Override注解校验格式。
3. 访问权限要求
子类重写方法的访问权限修饰符不能严于父类,权限等级:public > protected > 缺省 > private。例如父类方法为protected,子类可改为protected或public,不能改为缺省或private。
面试题 2 参考答案
1. 错误原因
子类Teacher的构造器未调用父类People的构造器。父类仅有有参构造器,无默认无参构造器,子类构造器默认隐式调用super()(父类无参构造器),导致编译报错。
2. 修复后代码
java
class People {private String name;private int age;
public People(String name, int age) {this.name = name;this.age = age;}
public String getName() { return name; }public void setName(String name) { this.name = name; }public int getAge() { return age; }public void setAge(int age) { this.age = age; }
}
class Teacher extends People {private String subject;
// 显式调用父类有参构造器(位于第一行)public Teacher(String name, int age, String subject) {super(name, age); // 关键修复this.subject = subject;}
public void teach() {System.out.println(getName() + "老师教" + subject); // 用getter访问私有成员}
}3. this(...)与super(...)能否共存
不能共存。两者都必须位于构造器的第一行,用于初始化对象(this(...)调用本类其他构造器,super(...)调用父类构造器),同一行无法同时执行两个初始化逻辑。
面试题 3 参考答案
1. 运行结果
动物 小狗汪汪叫
2. 结果原因与访问规则
原因:多态下 “方法重写生效,属性无多态”。
方法:父类引用
animal调用cry()时,实际执行子类Dog重写的方法;属性:属性不支持多态,直接访问父类
Animal的name属性。
访问规则:
方法:先找子类重写的方法,无则向上追溯父类方法;
属性:直接访问引用类型(父类)的属性,与对象真实类型无关。
3. 多态实现前提
存在继承或实现关系(类继承类、类实现接口);
父类引用指向子类对象(
父类名 变量 = new 子类名());存在方法重写(属性无多态)。
面试题 4 参考答案
1. 报错原因
多态下父类引用People无法直接调用子类特有方法(study()、teach()是子类独有,父类无定义),需通过强制类型转换(向下转型)解决。
2. 修复后代码
java
class People {protected String name;
public People(String name) {this.name = name;}
public void show() {System.out.println("姓名:" + name);}
}
class Student extends People {public Student(String name) {super(name);}
public void study() {System.out.println(name + "学生正在学习");}
}
class Teacher extends People {public Teacher(String name) {super(name);}
public void teach() {System.out.println(name + "老师正在授课");}
}
public class CastDemo {public static void main(String[] args) {People p1 = new Student("张三");People p2 = new Teacher("李四");
// 强制类型转换前用instanceof判断if (p1 instanceof Student) {Student s = (Student) p1;s.study(); // 调用子类特有方法}
if (p2 instanceof Teacher) {Teacher t = (Teacher) p2;t.teach(); // 调用子类特有方法}}
}3. instanceof的作用与使用
作用:判断对象的真实类型,避免强制类型转换时出现
ClassCastException(类型转换异常);使用格式:
对象 instanceof 类型,返回boolean值(true表示对象是该类型或其子类实例)。
面试题 5 参考答案
1. 代码错误与原因
错误 1:
SubClass继承FinalClass报错 →FinalClass被final修饰,不可被继承;错误 2:
SubClass重写show()方法报错 →show()被final修饰,不可被重写;错误 3:
fc.INFO = "修改常量"报错 →INFO是final变量,值不可修改。
2. final的核心作用
修饰类:类不可被继承(如
String类),避免核心逻辑被篡改;修饰方法:方法不可被重写,确保父类方法逻辑稳定;
修饰变量:
基本类型:变量值不可修改(常量);
引用类型:变量指向的地址不可修改,但对象内容可修改(如
final List list = new ArrayList<>(); list.add("a")合法)。
3. static final与普通final变量的区别
| 对比维度 | static final变量(全局常量) | 普通final变量(实例常量) |
|---|---|---|
| 所属对象 | 属于类,内存中仅一份 | 属于对象,每个对象一份 |
| 初始化时机 | 类加载时初始化 | 对象创建时初始化 |
| 访问方式 | 类名。变量名(推荐) | 对象名。变量名 |
| 命名规范 | 大写字母 + 下划线(如MAX_VALUE) | 驼峰命名(如finalName) |
面试题 6 参考答案
1. 完整代码实现
java
// 抽象类Shape
abstract class Shape {protected String type;
public Shape(String type) {this.type = type;}
// 抽象方法:计算面积(子类必须实现)public abstract double calculateArea();
// 普通方法:展示图形类型public void show() {System.out.println("图形类型:" + type);}
}
// 圆形类(子类)
class Circle extends Shape {private double radius; // 半径
public Circle(double radius) {super("圆形");this.radius = radius;}
@Overridepublic double calculateArea() {return Math.PI * radius * radius; // 圆面积公式:πr²}
}
// 矩形类(子类)
class Rectangle extends Shape {private double width; // 宽private double height; // 高
public Rectangle(double width, double height) {super("矩形");this.width = width;this.height = height;}
@Overridepublic double calculateArea() {return width * height; // 矩形面积公式:长×宽}
}
// 测试类
public class ShapeTest {public static void main(String[] args) {// 多态创建对象Shape circle = new Circle(5);Shape rectangle = new Rectangle(4, 6);
// 调用方法circle.show();System.out.println("圆形面积:" + String.format("%.2f", circle.calculateArea()));
rectangle.show();System.out.println("矩形面积:" + rectangle.calculateArea());}
}2. 抽象类能否创建对象
不能。抽象类是 “不完整的类”,包含未实现的抽象方法,仅作为父类供子类继承,若允许创建对象,调用抽象方法会导致逻辑错误。
3. 子类不重写所有抽象方法的后果
子类必须重写父类所有抽象方法,否则子类需被定义为抽象类(添加abstract修饰),否则编译报错。
面试题 7 参考答案
1. 运行结果
代码能正常运行,结果:
plaintext
雄鹰展翅高飞 自定义类型:飞行生物 飞行规则:遵守空中交通 飞行生物
2. 接口核心特性
接口不能创建对象,仅用于被类实现或被接口继承;
接口支持多继承(一个接口可继承多个接口)和多实现(一个类可实现多个接口);
接口成员默认访问权限为
public(抽象方法、默认方法、静态方法、常量);接口中的常量默认被
public static final修饰,必须显式赋值。
3. JDK8 后接口新增方法类型及访问规则
| 方法类型 | 定义格式 | 访问规则 |
|---|---|---|
| 默认方法 | default 返回值类型 方法名() | 实现类可直接调用或重写,通过对象调用 |
| 静态方法 | static 返回值类型 方法名() | 接口名直接调用,实现类不能重写 |
| 私有方法 | private 返回值类型 方法名() | 仅接口内部调用,用于封装默认方法的重复逻辑 |
面试题 8 参考答案
1. 场景适合的实现方式及原因
适合用 “抽象类 + 接口” 结合实现,或单独用抽象类实现:
原因:所有支付方式都需遵守 “支付安全校验” 的固定流程(适合抽象类的普通方法),同时需实现 “发起支付” 和 “查询支付状态” 的个性化功能(适合抽象方法或接口方法)。
2. 抽象类与接口的核心区别
| 对比维度 | 抽象类 | 接口 |
|---|---|---|
| 继承关系 | 单继承(一个类仅能继承一个抽象类) | 多实现(一个类可实现多个接口)、多继承(接口可继承多个接口) |
| 成员类型 | 抽象方法、普通方法、变量、构造器 | 抽象方法、默认方法、静态方法、私有方法、常量 |
| 设计理念 | 体现 “is-a” 关系(子类是父类的一种) | 体现 “has-a” 关系(类具备某种行为) |
3. 核心代码框架
java
// 抽象类:封装固定流程和通用属性
abstract class Payment {protected String payName; // 支付名称
public Payment(String payName) {this.payName = payName;}
// 固定流程:支付安全校验public final void securityCheck() {System.out.println(payName + "执行支付安全校验...");}
// 抽象方法:发起支付(子类个性化实现)public abstract boolean pay(double amount);
// 抽象方法:查询支付状态(子类个性化实现)public abstract String queryStatus(String orderId);
}
// 微信支付实现类
class WeChatPay extends Payment {public WeChatPay() {super("微信支付");}
@Overridepublic boolean pay(double amount) {securityCheck();System.out.println("微信支付成功,金额:" + amount + "元");return true;}
@Overridepublic String queryStatus(String orderId) {return "微信支付订单[" + orderId + "]:已支付";}
}
// 支付宝支付实现类
class Alipay extends Payment {public Alipay() {super("支付宝支付");}
@Overridepublic boolean pay(double amount) {securityCheck();System.out.println("支付宝支付成功,金额:" + amount + "元");return true;}
@Overridepublic String queryStatus(String orderId) {return "支付宝支付订单[" + orderId + "]:已支付";}
}面试题 9 参考答案
1. 饿汉式单例的优缺点
优点:
线程安全(类加载时创建实例,仅一次);
实现简单,无锁机制,性能高。
缺点:
类加载时即创建实例,提前占用内存(若实例未被使用,造成内存浪费);
无法延迟加载(懒加载)。
2. 懒汉式问题与修复
问题:多线程环境下,多个线程同时进入
if (instance == null),会创建多个实例,破坏单例特性;线程安全的懒汉式(双重检查锁定 + volatile):
java
class LazySingleton {// volatile防止指令重排private static volatile LazySingleton instance;
// 私有构造器,禁止外部创建对象private LazySingleton() {}
public static LazySingleton getInstance() {// 第一次检查:避免已创建实例后仍加锁if (instance == null) {synchronized (LazySingleton.class) {// 第二次检查:防止多线程等待锁时,实例已被创建if (instance == null) {instance = new LazySingleton();}}}return instance;}
}3. 单例模式应用场景
场景:任务管理器、数据库连接池、配置工具类、日志工具类等;
原因:这些场景需要确保对象唯一,避免重复创建导致资源浪费(如数据库连接池重复创建会耗尽连接)或逻辑冲突(如日志工具类多实例会导致日志混乱)。
面试题 10 参考答案
1. 枚举类与测试类代码
java
// 订单状态枚举类
enum OrderStatus {// 枚举常量(名称+中文描述)PENDING_PAY("待支付"),PAID("已支付"),SHIPPED("已发货"),COMPLETED("已完成"),CANCELLED("已取消");
// 中文描述属性private final String desc;
// 枚举构造器(默认私有)OrderStatus(String desc) {this.desc = desc;}
// 获取中文描述的方法public String getDesc() {return desc;}
// 静态方法:根据名称获取枚举常量及描述public static OrderStatus getByDesc(String desc) {for (OrderStatus status : values()) {if (status.getDesc().equals(desc)) {return status;}}return null;}
}
// 测试类
public class OrderStatusTest {public static void main(String[] args) {// 遍历所有枚举常量System.out.println("所有订单状态:");for (OrderStatus status : OrderStatus.values()) {System.out.println("状态名称:" + status.name() + ",中文描述:" + status.getDesc());}
// 根据描述获取枚举常量OrderStatus status = OrderStatus.getByDesc("已支付");System.out.println("\n根据描述查询:" + status.name());}
}2. 枚举类核心特性
枚举类默认继承
java.lang.Enum,不可被继承;构造器私有(默认,无需显式声明),外部不能创建对象;
第一行必须是枚举常量(本质是枚举类的实例);
提供
values()方法(编译器自动生成),返回所有枚举常量数组;提供
valueOf(String name)方法,根据名称获取枚举常量。
3. 枚举类相比普通类的优势
类型安全:枚举常量是唯一实例,避免普通类常量的类型错误(如用整数常量时传入非法值);
简化代码:无需手动实现单例、常量管理,编译器自动生成相关方法;
扩展性强:支持添加属性和方法,可实现接口,功能更丰富;
可读性高:枚举常量名称直观,代码逻辑清晰。
面试题 11 参考答案
1. 运行结果
plaintext
静态代码块1 静态代码块2 实例代码块1 实例代码块2 无参构造器 -------- 实例代码块1 实例代码块2 无参构造器
2. 静态代码块与实例代码块的核心区别
| 对比维度 | 静态代码块 | 实例代码块 |
|---|---|---|
| 执行时机 | 类加载时执行 | 对象创建时执行(构造器之前) |
| 执行次数 | 仅一次(类加载仅一次) | 每次创建对象都执行 |
| 访问权限 | 仅能访问静态成员(静态变量、静态方法) | 可访问静态成员和实例成员 |
| 核心作用 | 初始化静态变量、加载资源(如配置文件) | 初始化实例变量、提取构造器重复逻辑 |
3. 整体执行顺序
父类静态代码块 → 子类静态代码块(类加载阶段);
父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器(对象创建阶段)。
面试题 12 参考答案
1. 代码错误与原因
错误 1:
showOuter()方法访问innerName报错 → 外部类访问内部类成员需通过内部类对象,不能直接访问;错误 2:
new Inner()创建内部类对象报错 → 成员内部类需通过外部类对象创建(外部类对象.new 内部类())。
2. 修复后代码
java
class Outer {private String outerName = "外部类名称";static String outerStatic = "外部静态变量";
class Inner {private String innerName = "内部类名称";
public void showInner() {System.out.println(innerName);System.out.println(outerName); // 内部类可直接访问外部类成员System.out.println(outerStatic);}}
public void showOuter() {System.out.println(outerName);// 外部类访问内部类成员:通过内部类对象Inner inner = new Inner();System.out.println(inner.innerName);System.out.println(outerStatic);}
}
public class InnerClassDemo {public static void main(String[] args) {// 创建内部类对象:外部类对象.new 内部类()Outer outer = new Outer();Outer.Inner inner = outer.new Inner();inner.showInner();
// 调用外部类方法outer.showOuter();}
}3. 成员内部类的创建方式与访问规则
创建方式:
外部类名.内部类名 变量 = new 外部类().new 内部类();(需先创建外部类对象);访问规则:
内部类可直接访问外部类的所有成员(包括私有成员);
外部类访问内部类成员需通过内部类对象;
内部类中用
外部类名.this可获取外部类对象(如Outer.this.outerName)。
面试题 13 参考答案
1. Lambda 表达式改写后的main方法
java
import java.util.Arrays;
public class AnonymousLambdaDemo {public static void main(String[] args) {Student[] students = {new Student("张三", 20),new Student("李四", 18),new Student("王五", 22)};
// Lambda表达式:按年龄升序排序(简化匿名内部类)Arrays.sort(students, (s1, s2) -> s1.getAge() - s2.getAge());
System.out.println(Arrays.toString(students));}
}2. Lambda 核心语法与省略规则
核心语法:
(参数列表) -> { 方法体 };省略规则:
参数类型可省略(编译器自动推断);
若仅一个参数,括号
()可省略;若方法体仅一行代码,大括号
{}和分号;可省略;若为return语句,return需一并省略。
3. Lambda 使用前提
Lambda 表达式是函数式接口的匿名内部类简化写法,使用前提是:必须存在一个 “有且仅有一个抽象方法” 的函数式接口(可通过 @FunctionalInterface 注解校验)。
面试题 14 参考答案
1. 方法引用改写后的代码
java
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;
class Person {private String name;private int age;
public Person() {}
public Person(String name, int age) {this.name = name;this.age = age;}
@Overridepublic String toString() {return "姓名:" + name + ",年龄:" + age;}
}
public class MethodReferenceDemo {public static void main(String[] args) {// 1. 方法引用:字符串按长度比较(特定类型方法引用)Comparator<String> comp1 = String::compareToIgnoreCase; // 按长度比较可简化为 (s1,s2)->s1.length()-s2.length(),无直接方法引用,此处用忽略大小写比较示例String[] strs = {"apple", "banana", "orange"};Arrays.sort(strs, String::length); // 按长度比较的方法引用(特定类型方法引用)System.out.println(Arrays.toString(strs));
// 2. 方法引用:创建Person对象(构造器引用)Supplier<Person> sup1 = Person::new;Person p = sup1.get();System.out.println(p);}
}2. 方法引用的形式与适用场景
| 引用形式 | 语法格式 | 适用场景 |
|---|---|---|
| 静态方法引用 | 类名::静态方法 | Lambda 逻辑仅调用某个静态方法 |
| 实例方法引用 | 对象名::实例方法 | Lambda 逻辑仅调用某个对象的实例方法 |
| 特定类型方法引用 | 类名::实例方法 | Lambda 第一个参数是方法调用者,后续参数是方法参数 |
| 构造器引用 | 类名::new | Lambda 逻辑仅创建对象 |
3. 方法引用与 Lambda 的关系
方法引用是 Lambda 表达式的进一步简化形式,当 Lambda 表达式的逻辑仅为调用一个已存在的方法时,可通过方法引用替代重复的 Lambda 代码,使代码更简洁、可读性更高。
面试题 15 参考答案
1. 完整代码实现
java
运行
// 商品接口
interface Product {// 获取商品信息String getInfo();// 获取价格double getPrice();
}
// 电子产品实现类
class Electronics implements Product {private String name;private double price;private String brand; // 品牌
public Electronics(String name, double price, String brand) {this.name = name;this.price = price;this.brand = brand;}
@Overridepublic String getInfo() {return "电子产品 - 名称:" + name + ",品牌:" + brand + ",价格:" + price + "元";}
@Overridepublic double getPrice() {return price;}
}
// 服装实现类
class Clothing implements Product {private String name;private double price;private String size; // 尺寸
public Clothing(String name, double price, String size) {this.name = name;this.price = price;this.size = size;}
@Overridepublic String getInfo() {return "服装 - 名称:" + name + ",尺寸:" + size + ",价格:" + price + "元";}
@Overridepublic double getPrice() {return price;}
}
// 商品管理类
class ProductManager {private List<Product> productList = new ArrayList<>();
// 添加商品public void addProduct(Product product) {if (product != null) {productList.add(product);}}
// 查询所有商品public List<Product> queryAll() {return new ArrayList<>(productList); // 返回副本,避免外部修改}
// 按价格区间查询商品public List<Product> queryByPriceRange(double minPrice, double maxPrice) {List<Product> result = new ArrayList<>();for (Product product : productList) {double price = product.getPrice();if (price >= minPrice && price <= maxPrice) {result.add(product);}}return result;}
}
// 测试类
public class ProductSystemTest {public static void main(String[] args) {// 创建管理对象ProductManager manager = new ProductManager();
// 添加商品manager.addProduct(new Electronics("手机", 3999.99, "华为"));manager.addProduct(new Electronics("笔记本电脑", 5999.99, "联想"));manager.addProduct(new Clothing("T恤", 99.9, "M"));manager.addProduct(new Clothing("牛仔裤", 199.9, "L"));
// 查询所有商品System.out.println("所有商品:");List<Product> allProducts = manager.queryAll();for (Product product : allProducts) {System.out.println(product.getInfo());}
// 按价格区间查询(100~6000元)System.out.println("\n价格100~6000元的商品:");List<Product> rangeProducts = manager.queryByPriceRange(100, 6000);for (Product product : rangeProducts) {System.out.println(product.getInfo());}}
}2. 体现的 OOP 核心特性
封装:
ProductManager封装商品的添加、查询逻辑,隐藏内部存储细节(productList私有);接口:
Product接口定义商品的规范,实现类按需实现,体现 “面向接口编程”;多态:
productList存储Product接口引用,可接收Electronics、Clothing等任意实现类对象;继承 / 实现:
Electronics和Clothing实现Product接口,复用接口规范,实现个性化功能。
3. 扩展性分析
具备良好扩展性。新增 “食品” 商品类型时,只需创建Food类实现Product接口,无需修改现有ProductManager和测试类代码,符合 “开闭原则”(对扩展开放,对修改关闭)。
