当前位置: 首页 > news >正文

【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)默认比较对象地址==),重写后可自定义相等规则(如比较 nameage)。

    • hashCode():默认返回对象地址相关的哈希值,重写 equals 时必须重写(保证 “相等对象哈希值相等”)。

4. 方法重写(Override)—— 多态的前提

方法重写是子类对父类方法的 “重新实现”,需严格遵循方法签名一致性访问权限规则,否则编译报错。

(1)重写的严格规则(编译器校验逻辑)
  1. 方法签名完全一致

    :方法名、参数列表(个数、类型、顺序)必须与父类完全相同。

    ❌ 错误示例:父类

    eat(String food)

    ,子类

    eat(int num)

    (参数类型不同,不是重写,是重载)。

  2. 访问权限不缩小

    :子类重写方法的权限 ≥ 父类方法(

    public > protected > 缺省

    )。

    ❌ 错误示例:父类

    public void run()

    ,子类

    protected void run()

    (权限缩小,编译报错)。

  3. 返回值类型兼容

    • JDK7 前:必须与父类完全相同。

    • JDK7 后:支持 “协变返回值”—— 子类返回值可是父类返回值的子类(如父类返回 Animal,子类返回 Dog)。

  4. 异常声明不扩大:子类重写方法声明的异常 ≤ 父类方法(异常类型可相同或为子类)。

  5. 不可重写的方法

    • 父类 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() 编译报错,因 Parentstudy() 方法)。

(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.Stringjava.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. 赋值给变量),确保其他线程看到 instancenull 时,对象已完全初始化。

(3)枚举单例 —— 最安全的实现
  • 实现代码

    java

    运行

    public enum EnumSingleton {INSTANCE; // 唯一实例(枚举常量)// 枚举类的方法public void doSomething() {System.out.println("枚举单例执行方法");}
    }

  • 底层优势

    1. 枚举类默认私有构造器,外部无法创建对象。

    2. 枚举类的实例由 JVM 管理,确保线程安全(枚举类加载时初始化实例)。

    3. 避免反射破坏单例(反射无法创建枚举类的实例)。

  • 应用场景:高安全性场景(如配置中心、数据库连接池)。

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 + "汪汪叫");}
    }

  • 核心规则

    1. 抽象类不能创建对象(new Animal() 报错),仅用于继承。

    2. 子类继承抽象类,必须重写所有抽象方法(否则子类需用 abstract 修饰)。

    3. 抽象类可包含普通方法、成员变量、构造器(构造器用于子类初始化父类部分)。

(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 新增方法(解决接口扩展问题)

    1. 默认方法(default)

      :有方法体,实现类可直接调用或重写,用于接口扩展(避免修改所有实现类)。

      java

      interface Swim {default void warmUp() { // 默认方法System.out.println("热身运动");}void swim();
      }
      ​
      class Fish implements Swim {@Overridepublic void swim() {System.out.println("鱼游");}// 可重写warmUp(),也可不重写(直接使用默认实现)
      }

    2. 静态方法(static)

      :有方法体,接口名直接调用,用于定义工具方法。

      java

      interface Swim {static void showRule() { // 静态方法System.out.println("游泳规则:禁止潜水");}void swim();
      }
      ​
      // 使用
      Swim.showRule(); // 接口名直接调用

    3. 私有方法(private,JDK9+):有方法体,仅接口内部调用,用于封装默认方法或静态方法的重复逻辑。

(2)接口的核心特点
  1. 多实现

    :一个类可实现多个接口(

    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("鸟飞");}
    }

  2. 多继承

    :一个接口可继承多个接口(

    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. 接口冲突解决

    • 实现多个接口,若有同名默认方法:实现类必须重写该方法(否则编译报错)。

    • 继承父类且实现接口,若父类方法与接口默认方法同名:优先使用父类方法(“类优先” 原则)。

(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 {})
  • 执行时机:类加载时执行(仅一次,无论创建多少对象)。

  • 执行顺序:多个静态代码块按定义顺序执行;父类静态代码块 → 子类静态代码块。

  • 核心作用

    1. 初始化静态变量(如加载配置文件、初始化全局常量)。

    2. 执行类级别的初始化逻辑(如注册驱动)。

  • 示例

    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)实例代码块({})
  • 执行时机:每次创建对象时执行(在构造器前执行)。

  • 执行顺序:多个实例代码块按定义顺序执行;父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器。

  • 核心作用

    1. 初始化实例变量(提取多个构造器的重复初始化逻辑)。

    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 内部类();

  • 核心特点

    1. 内部类持有外部类的引用(外部类名.this),可直接访问外部类的所有成员(包括 private 成员)。

    2. 外部类访问内部类成员,需通过内部类实例。

  • 示例

    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 外部类名.内部类名();

  • 核心特点

    1. 不持有外部类引用,仅可直接访问外部类的静态成员(无法访问实例成员,需通过外部类实例)。

    2. 可独立存在(类似普通类,只是定义在外部类内部)。

  • 示例

    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.HashMapEntry 内部类,用于存储键值对)。

(3)局部内部类(方法内的内部类)
  • 定义:定义在方法、代码块、构造器等局部范围内的内部类,作用域仅限局部范围。

  • 核心特点

    1. 访问局部变量时,局部变量必须是

      final

      修饰(JDK8 后隐式

      ,即变量值不可修改)。

      ✅ 原因:局部变量的生命周期与内部类对象生命周期不一致 —— 局部变量随方法执行结束销毁,而内部类对象可能仍存在(如被外部引用),

      final

      确保变量值不被修改,避免内部类访问到 “已销毁的变量”。

    2. 实用性低(仅在局部范围使用,无法外部访问),开发中极少使用。

  • 示例

    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 方法名() {// 方法实现}
    };

  • 核心特点

    1. 无类名,编译器自动生成类名(如 Outer$1.class)。

    2. 本质是子类对象,创建后立即使用(仅一次)。

    3. 仅能继承一个父类或实现一个接口(不能同时)。

  • 开发中的核心应用场景

    1. 作为方法参数传递(简化代码,替代显式子类)。

      java

      // 接口
      interface Swim {void swim();
      }
      ​
      // 方法:参数为Swim接口
      public static void testSwim(Swim s) {s.swim();
      }
      ​
      // 使用匿名内部类作为参数
      testSwim(new Swim() {@Overridepublic void swim() {System.out.println("匿名内部类实现游泳");}
      });

    2. 事件监听(如 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 的简化规则(核心)
  1. 参数类型可省略:编译器根据函数式接口的抽象方法自动推断参数类型。

  2. 参数括号可省略:若仅一个参数,() 可省略;多个参数不可省略。

  3. 方法体大括号可省略:若方法体仅一行代码,{}; 可省略;若有 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)
构造器引用类名::newLambda 逻辑仅创建对象,且参数列表与构造器一致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[] valuefinal 修饰数组,地址不可变;private 修饰,外部无法修改数组元素)。

  • JDK9 及之后:底层是 private final byte[] value + private final byte coder(编码标识),节省内存(如 Latin-1 编码的字符串,1 个字符占 1 字节,而 char 占 2 字节)。

  • 不可变性的体现:任何修改 String 的操作(如 substringreplace)都会创建新的 String 对象,原对象不变。

(2)String 常量池 —— 避免重复创建对象
  • 定义:方法区中的一块特殊区域,存储字符串常量(如 "abc"),避免重复创建相同内容的 String 对象。

  • 核心机制

    1. 直接赋值创建 String(String s = "abc"):先检查常量池,若存在 "abc",直接返回引用;若不存在,创建常量并放入常量池

    2. 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[] elementDatatransient 修饰,序列化时仅序列化实际元素,而非整个数组)。

  • 容量与大小

    • 容量(Capacity):elementData 数组的长度(默认初始容量 10)。

    • 大小(Size):集合中实际元素的个数(size 变量记录)。

(2)ArrayList 的扩容机制

当添加元素时,若 size == 容量,触发扩容:

  1. 扩容公式:新容量 = 旧容量 + (旧容量>> 1)(即旧容量的 1.5 倍,如 10→15→22...)。

  2. 扩容过程

    • 创建新容量的数组(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;}
}

问题

  1. 代码存在什么问题?请修复并写出完整正确代码。

  2. 方法重写(Override)的核心规则有哪些?

  3. 子类重写父类方法时,访问权限修饰符有什么要求?


面试题 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);}
}

问题

  1. 代码编译错误的原因是什么?

  2. 修复代码使其能正常运行,写出完整修复代码。

  3. 子类构造器中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();}
}

问题

  1. 代码的运行结果是什么?

  2. 为什么会出现该结果?多态下属性和方法的访问规则有何不同?

  3. 多态的实现前提有哪些?


面试题 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(); // 报错}
}

问题

  1. 代码报错的原因是什么?

  2. 修复代码使其能正确调用子类特有方法,写出完整修复代码。

  3. 强制类型转换前为什么需要用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);}
}

问题

  1. 代码存在哪些错误?分别说明原因。

  2. final修饰类、方法、变量的核心作用是什么?

  3. static final修饰的常量与普通final变量有何区别?


面试题 6(中级:抽象类的核心规则)

场景

面试官:“请基于抽象类设计一个Shape(图形)体系,包含Circle(圆形)和Rectangle(矩形),要求:1. Shape为抽象类,包含抽象方法calculateArea()(计算面积)和普通方法show()(展示图形类型);2. 子类必须实现calculateArea()方法;3. 测试类通过多态创建对象并计算面积。”

问题

  1. 写出完整的代码实现(包含ShapeCircleRectangle和测试类)。

  2. 抽象类能否创建对象?为什么?

  3. 子类继承抽象类后,若不重写所有抽象方法,会有什么后果?


面试题 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);}
}

问题

  1. 代码能否正常运行?若能,运行结果是什么?若不能,修复错误。

  2. 接口的核心特性有哪些?(至少 3 点)

  3. JDK8 后接口新增了哪几种方法?各自的访问规则是什么?


面试题 8(中高:抽象类 vs 接口的区别与选择)

场景

面试官:“某电商平台需要设计支付功能,支持微信支付、支付宝支付、银联支付,每种支付方式都有‘发起支付’和‘查询支付状态’两个核心功能,同时所有支付方式都需要遵守‘支付安全校验’的固定流程。请回答以下问题:”

问题

  1. 该场景适合用抽象类还是接口实现?为什么?

  2. 抽象类和接口的核心区别有哪些?(从继承关系、成员类型、设计理念 3 个维度说明)

  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;}
}

问题

  1. 饿汉式单例的优缺点是什么?

  2. 懒汉式单例存在什么问题?如何修复(写出线程安全的懒汉式代码)?

  3. 单例模式的应用场景有哪些?为什么这些场景需要单例?


面试题 10(中高:枚举类的特性与应用)

场景

面试官:“请设计一个枚举类OrderStatus,表示订单的状态(待支付、已支付、已发货、已完成、已取消),要求:1. 每个状态包含对应的中文描述;2. 提供方法根据状态名称获取中文描述;3. 测试类遍历所有状态并打印。”

问题

  1. 写出完整的OrderStatus枚举类和测试类代码。

  2. 枚举类的核心特性有哪些?(至少 3 点)

  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();}
}

问题

  1. 代码的运行结果是什么?

  2. 静态代码块和实例代码块的核心区别是什么?(执行时机、执行次数、访问权限)

  3. 代码块、构造器、父类初始化的整体执行顺序是什么?


面试题 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();}
}

问题

  1. 代码存在哪些错误?分别说明原因。

  2. 修复代码使其能正常运行,写出完整修复代码。

  3. 成员内部类的创建方式是什么?内外类相互访问成员的规则是什么?


面试题 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));}
}

问题

  1. 将匿名内部类改写为 Lambda 表达式,写出完整的main方法代码。

  2. Lambda 表达式的核心语法是什么?有哪些省略规则?

  3. 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);}
}

问题

  1. 将两处 Lambda 表达式改写为方法引用,写出完整代码。

  2. 方法引用有哪几种形式?各自的适用场景是什么?

  3. 方法引用与 Lambda 表达式的关系是什么?


面试题 15(高级:综合应用:接口 + 多态 + 集合)

场景

面试官:“请设计一个简易的商品管理系统,要求:1. 定义Product接口,包含getInfo()(获取商品信息)和getPrice()(获取价格)方法;2. 实现Electronics(电子产品)和Clothing(服装)两个类,分别实现Product接口;3. 定义ProductManager类,包含addProduct()(添加商品)、queryAll()(查询所有商品)、queryByPriceRange()(按价格区间查询商品)方法;4. 测试类完成商品添加、查询功能。”

问题

  1. 写出完整的代码实现(包含接口、实现类、管理类、测试类)。

  2. 该设计中体现了 OOP 的哪些核心特性?请具体说明。

  3. 若需新增 “食品” 商品类型,该设计是否具备扩展性?为什么?


参考答案

面试题 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. 方法重写核心规则

  1. 方法名、参数列表必须与父类完全一致;

  2. 返回值类型:与父类一致或为其子类(协变返回值);

  3. 访问权限:子类方法权限 ≥ 父类方法权限;

  4. 私有方法、静态方法不能重写;

  5. 可添加@Override注解校验格式。

3. 访问权限要求

子类重写方法的访问权限修饰符不能严于父类,权限等级:public > protected > 缺省 > private。例如父类方法为protected,子类可改为protectedpublic,不能改为缺省或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重写的方法;

    • 属性:属性不支持多态,直接访问父类Animalname属性。

  • 访问规则:

    • 方法:先找子类重写的方法,无则向上追溯父类方法;

    • 属性:直接访问引用类型(父类)的属性,与对象真实类型无关。

3. 多态实现前提

  1. 存在继承或实现关系(类继承类、类实现接口);

  2. 父类引用指向子类对象(父类名 变量 = new 子类名());

  3. 存在方法重写(属性无多态)。

面试题 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报错 → FinalClassfinal修饰,不可被继承;

  • 错误 2:SubClass重写show()方法报错 → show()final修饰,不可被重写;

  • 错误 3:fc.INFO = "修改常量"报错 → INFOfinal变量,值不可修改。

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. 接口核心特性

  1. 接口不能创建对象,仅用于被类实现或被接口继承;

  2. 接口支持多继承(一个接口可继承多个接口)和多实现(一个类可实现多个接口);

  3. 接口成员默认访问权限为public(抽象方法、默认方法、静态方法、常量);

  4. 接口中的常量默认被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. 饿汉式单例的优缺点

  • 优点:

    1. 线程安全(类加载时创建实例,仅一次);

    2. 实现简单,无锁机制,性能高。

  • 缺点:

    1. 类加载时即创建实例,提前占用内存(若实例未被使用,造成内存浪费);

    2. 无法延迟加载(懒加载)。

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. 枚举类核心特性

  1. 枚举类默认继承java.lang.Enum,不可被继承;

  2. 构造器私有(默认,无需显式声明),外部不能创建对象;

  3. 第一行必须是枚举常量(本质是枚举类的实例);

  4. 提供values()方法(编译器自动生成),返回所有枚举常量数组;

  5. 提供valueOf(String name)方法,根据名称获取枚举常量。

3. 枚举类相比普通类的优势

  1. 类型安全:枚举常量是唯一实例,避免普通类常量的类型错误(如用整数常量时传入非法值);

  2. 简化代码:无需手动实现单例、常量管理,编译器自动生成相关方法;

  3. 扩展性强:支持添加属性和方法,可实现接口,功能更丰富;

  4. 可读性高:枚举常量名称直观,代码逻辑清晰。

面试题 11 参考答案

1. 运行结果

plaintext

静态代码块1
静态代码块2
实例代码块1
实例代码块2
无参构造器
--------
实例代码块1
实例代码块2
无参构造器

2. 静态代码块与实例代码块的核心区别

对比维度静态代码块实例代码块
执行时机类加载时执行对象创建时执行(构造器之前)
执行次数仅一次(类加载仅一次)每次创建对象都执行
访问权限仅能访问静态成员(静态变量、静态方法)可访问静态成员和实例成员
核心作用初始化静态变量、加载资源(如配置文件)初始化实例变量、提取构造器重复逻辑

3. 整体执行顺序

  1. 父类静态代码块 → 子类静态代码块(类加载阶段);

  2. 父类实例代码块 → 父类构造器 → 子类实例代码块 → 子类构造器(对象创建阶段)。

面试题 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 内部类();(需先创建外部类对象);

  • 访问规则:

    1. 内部类可直接访问外部类的所有成员(包括私有成员);

    2. 外部类访问内部类成员需通过内部类对象;

    3. 内部类中用 外部类名.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 核心语法与省略规则

  • 核心语法:(参数列表) -> { 方法体 }

  • 省略规则:

    1. 参数类型可省略(编译器自动推断);

    2. 若仅一个参数,括号 () 可省略;

    3. 若方法体仅一行代码,大括号 {} 和分号 ; 可省略;若为 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 第一个参数是方法调用者,后续参数是方法参数
构造器引用类名::newLambda 逻辑仅创建对象

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 核心特性

  1. 封装:ProductManager封装商品的添加、查询逻辑,隐藏内部存储细节(productList私有);

  2. 接口:Product接口定义商品的规范,实现类按需实现,体现 “面向接口编程”;

  3. 多态:productList存储Product接口引用,可接收ElectronicsClothing等任意实现类对象;

  4. 继承 / 实现:ElectronicsClothing实现Product接口,复用接口规范,实现个性化功能。

3. 扩展性分析

具备良好扩展性。新增 “食品” 商品类型时,只需创建Food类实现Product接口,无需修改现有ProductManager和测试类代码,符合 “开闭原则”(对扩展开放,对修改关闭)。

http://www.dtcms.com/a/541049.html

相关文章:

  • 菲林式投影灯成像模糊?OAS 软件精准优化破瓶颈
  • 匹配最接近的行政区域sql 反向匹配
  • ROS2系列 (6) : 多功能包工作空间(Workspace)最佳实践
  • Nacos动态刷新实战:客户端集成与案例验证
  • 谷歌网站怎么做排名pc端手机网站 viewport 自适应
  • 建设银行衡阳市分行网站数字营销
  • 淄博网站建设卓迅科技有限公司属于什么企业类型
  • 梅州企业网站wap网站推荐
  • 14、Docker swarm-1-理论
  • Jenkins Share Library教程 —— 企业级 Jenkins Shared Library 实战示例
  • 做微新闻怎么发视频网站seo网站沙盒期
  • 中国建设信息港网站wordpress开源程序建站教程
  • Win11 跨设备同步的便笺内容突然丢失,如何恢复?
  • 三、cmake语法-提高篇
  • 仓颉编程(20)泛型
  • Go语言2D游戏开发入门004:零基础打造射击游戏《太空大战》3
  • 学习FreeRTOS(FreeRTOS移植到STM32F103C8T6)
  • json缩放 json 缩放
  • maxkb部署,版本升级步骤与注意事项(超详细图文)
  • 测试开发话题02---概念篇
  • 网站建设推广优化排名全国工商核名查询系统官网
  • ASP Content Linking
  • 【研究生随笔】Pytorch中的卷积神经网络(1)
  • Android运行项目报错集合
  • 为什么电脑会蓝屏?怎么快速解决电脑蓝屏问题
  • js建设网站html5网站开发价格
  • ESP32学习笔记(基于IDF):OneNET物模型数据交互(控制小灯)
  • JavaScript逆向与爬虫实战——基础篇(css反爬之动态字体实现原理及绕过)
  • 浏览器在请求 js/css 静态文件时,服务器返回了 500 内部错误 500 Internal Server Error
  • CSS 实现酷炫的不规则圆角与斜角边框效果(四种方法详解)