这是一份为你定制的Java接口（Interface）与抽象类（Abstract Class）终极对比笔记。这份笔记整合了从Java 7到Java 17的所有语法变迁，并深入到设计哲学和底层JVM机制，适合面试冲刺或作为案头速查手册。📌 第一部分：核心定义（先定调）抽象类（Abstract Class）：“不完整的实体”。它用来描述一类事物公共的“本质”（状态+行为）。它可以有构造器、有属性、有已实现的方法，但因为不够具体，所以不允许被new出来。接口（Interface）：“纯粹的契约/能力”。它用来定义一种“行为规范”。它不管实现者是谁（可以是鸟、飞机、超人），只要签署了这份契约（implements），就必须具备这个能力。📌 第二部分：终极语法速查表（Java 8+ 特别版）对比维度抽象类（Abstract Class）接口（Interface）关键字abstract classinterface实例化（new）不能（无法创建对象）不能（无法创建对象）构造方法可以有（用于子类初始化父类属性）绝对没有（因为无实例变量需初始化）成员变量可以是任何类型（private/protected/public 实例变量，可以有状态）默认且只能是 public static final（常量），变量必须显式赋初值普通具体方法可以有（直接写带{}的方法体）Java 8+ 通过 default 和 static 可以有；Java 9+ 通过 private 可以有抽象方法使用 abstract 关键字，子类必须实现（除非子类也是抽象类）默认隐含 public abstract，实现类必须实现（除非实现类是抽象类）继承/实现关系单继承（一个类只能 extends 一个父类）多继承/多实现（一个接口可以 extends 多个接口；一个类可以 implements 多个接口）访问修饰符限制方法可以是 public/protected/private/default接口中的抽象方法隐含且只能是 public（不能是private或protected，否则编译报错）静态方法可以有，子类可以继承调用可以有（Java 8+），但只能通过接口名调用，实现类无法继承final 修饰可以有 final 方法（禁止子类重写）接口中的方法不能声明为 final（因为抽象/默认方法天生需要被重写或覆盖）📌 第三部分：方法家族全览（最易混淆点详解）接口到底能写哪些方法？这是面试高频扣分点：方法类型是否允许（接口）是否允许（抽象类）备注抽象方法（无方法体）✅ 允许（隐含public abstract）✅ 允许接口只干这事（Java 7之前）实例具体方法（有方法体）❌ 不允许（直到Java 8）✅ 允许抽象类主打的功能default 方法（Java 8+）✅ 允许—用于接口版本升级，避免老实现类报错static 方法（Java 8+）✅ 允许✅ 允许接口的static不可继承；抽象类的static可被子类继承调用private 方法（Java 9+）✅ 允许✅ 允许接口内用于抽取default方法的公共逻辑，不对外暴露📌 第四部分：设计哲学（本质区别，面试必答）抽象类：本质是“是什么”（Is-A）。侧重于对象本身。子类和父类必须有血缘关系（例如：Dog extends Animal）。用于捕获公共的状态（属性）和通用的基础逻辑。例子：所有订单都有订单号、金额，创建时都要打印日志。这些状态和步骤必须由抽象类来定义，接口做不到。接口：本质是“能做什么”（Can-Do / Like-A）。侧重于对象拥有的能力。完全不相关的类可以通过实现同一个接口获得相同能力（例如：Bird、Plane、Superman 都能实现 Flyable）。用于定义行为契约，不关心实现者的内部状态。例子：Comparable（可比较）、Runnable（可运行）、Serializable（可序列化）。📌 第五部分：经典协作模式（源码级的黄金搭档）在顶级框架（如Spring、JDK集合框架）中，它们从不“单打独斗”，而是按照 “接口定义标准 + 抽象类提供骨架 + 具体类实现业务” 三层结构配合： [List 接口] ← 定义契约（能添加、能删除、能遍历） ↑ (implements) [AbstractList 抽象类] ← 提供骨架（把 indexOf、equals 等通用逻辑先写一半） ↑ (extends) ┌──────────┴──────────┐ [ArrayList] [LinkedList] ← 具体实现（各自实现最核心的差异化逻辑）结论：用接口应对多态和扩展，用抽象类应对代码复用和封装状态。📌 第六部分：面试高频陷阱题（查漏补缺）抽象类能实现接口吗？ ✅ 可以。且抽象类实现接口时可以不实现接口的方法，留给具体子类去实现（这就是适配器模式的底层原理）。接口能继承抽象类吗？ ❌ 绝对不可以。接口只能 extends 接口，不能 extends 类（语法上禁止）。什么时候用 default 方法重写？ 一个类实现了两个接口，两个接口都有同名的 default 方法，此时实现类必须显式重写该方法，否则编译冲突。抽象类里的 static 方法和接口里的 static 方法区别？ 抽象类的 static 方法可以通过子类名直接调用（继承）；接口的 static 方法只能通过接口名调用，子类/实现类无法继承。📌 第七部分：极简决策脑图（实用场景）如果你的需求是： ├── 共享给子类公共的 属性（成员变量）？ │ └── 是 → 必须用 抽象类（接口存不住状态） ├── 需要 非public 的访问权限（protected/private）？ │ └── 是 → 必须用 抽象类 ├── 想要为 完全无关的类 提供通用能力（如序列化）？ │ └── 是 → 必须用 接口 ├── 想要利用 多重继承 的好处？ │ └── 是 → 必须用 接口 └── 想要为已有系统做功能增强（不破坏旧代码）？ └── 是 → 用接口的 default 方法（Java 8+）这份笔记涵盖了从语法到架构的全部精髓。如果你想基于这份笔记，手写一个“接口+抽象类”配合的实际Demo（比如模拟Spring的BeanFactory），我可以立刻给你写一份，让你彻底吃透它们的配合使用。😊
在 Spring Boot 2.7 的企业实际开发中，数组操作主要依赖 Java 标准库（java.util.Arrays）和 Spring 框架内置工具类（org.springframework.util 包），两者结合基本能覆盖所有场景。⚙️ 数组操作核心方法速查操作类型核心方法所属类/库关键说明排序Arrays.sort(array)java.util.Arrays对数组进行原地升序排序。二分查找Arrays.binarySearch(array, key)java.util.Arrays必须在已排序数组中使用。数组拷贝Arrays.copyOf(original, newLength)Arrays.copyOfRange(original, from, to)java.util.Arrays灵活复制数组全部或部分内容。数组填充Arrays.fill(array, value)java.util.Arrays将数组所有或指定范围元素设为同一个值。数组转字符串Arrays.toString(array)Arrays.deepToString(array)java.util.Arrays方便打印一维或多维数组内容。数组比较Arrays.equals(array1, array2)Arrays.deepEquals(array1, array2)java.util.Arrays比较一维或多维数组内容是否相等。集合判空CollectionUtils.isEmpty(collection)CollectionUtils.isNotEmpty(collection)org.springframework.util.CollectionUtils安全地检查集合是否为 null 或空。数组转集合CollectionUtils.arrayToList(array)org.springframework.util.CollectionUtils支持基本类型数组转换。数组合并到集合CollectionUtils.mergeArrayIntoCollection(array, collection)org.springframework.util.CollectionUtils将数组元素合并到现有集合中。集合空值兜底CollectionUtils.emptyIfNull(collection)org.springframework.util.CollectionUtils如果集合为 null，返回一个空集合，避免 NPE。集合包含检查CollectionUtils.contains(collection.iterator(), element)org.springframework.util.CollectionUtils检查集合是否包含某元素。对象/数组判空ObjectUtils.isEmpty(obj)org.springframework.util.ObjectUtils可检查对象、数组、集合等是否为 null 或空。获取首个非空对象ObjectUtils.firstNonNull(obj1, obj2, ...)org.springframework.util.ObjectUtils从多个对象中返回第一个非 null 的。数组转分隔字符串StringUtils.arrayToCommaDelimitedString(arr)org.springframework.util.StringUtils将数组元素用逗号连接成字符串。🚀 数组与集合互转（高频场景）这是日常开发中最常见的操作之一，需要特别注意不同方法的区别。数组 转 ListArrays.asList(T... a)：最常用，但返回的 List 是固定长度的，不能进行 add 或 remove 操作。且不支持基本类型数组。// 引用类型数组 -> 正确 String[] strArray = {"a", "b"}; List<String> list = Arrays.asList(strArray); // 基本类型数组 -> 错误！ list 实际上是一个包含 int[] 对象的 List int[] intArray = {1, 2}; List<int[]> list = Arrays.asList(intArray); new ArrayList<>(Arrays.asList(array))：创建一个可变的 ArrayList，可以自由增删元素。String[] strArray = {"a", "b"}; List<String> list = new ArrayList<>(Arrays.asList(strArray)); list.add("c"); // 可以操作CollectionUtils.arrayToList(array)：Spring 提供的工具方法，优雅地解决了基本类型数组的转换问题。int[] intArray = {1, 2}; // 自动将 int[] 转为 List<Integer> List<?> list = CollectionUtils.arrayToList(intArray); Arrays.stream(array).collect(Collectors.toList())：Stream 方式，灵活强大，适用于需要进行过滤、映射等复杂操作后再收集的场景。List 转 数组list.toArray(T[] a)：标准推荐方式。List<String> list = Arrays.asList("a", "b"); // 传入一个长度相同的空数组，类型安全 String[] array = list.toArray(new String[0]);list.stream().toArray()：Java 8 引入的 Stream 方式。List<String> list = Arrays.asList("a", "b"); String[] array = list.stream().toArray(String[]::new);✨ 高阶技巧：Stream 流式处理对于复杂的数组操作（过滤、转换、聚合），Stream API 是首选。int[] numbers = {1, 2, 3, 4, 5}; // 1. 过滤出偶数并打印 Arrays.stream(numbers).filter(n -> n % 2 == 0).forEach(System.out::println); // 输出 2, 4 // 2. 所有元素乘以2并收集为新的List List<Integer> doubledList = Arrays.stream(numbers) .map(n -> n * 2) .boxed() // 将 IntStream 装箱为 Stream<Integer> .collect(Collectors.toList());对于二维数组，Arrays.stream(arr) 得到的是 Stream<int[]>，即每一行作为一个元素。🧰 企业常用第三方库虽然标准库和 Spring 工具类已很强大，但在实际项目中，为了进一步简化代码、减少样板代码，往往会引入一些优秀的第三方库：Hutool：国产明星工具库，提供 ArrayUtil 等大量实用的数组操作方法。Guava：Google 出品，其 Lists、Ints 等工具类在集合和数组操作上非常强大。Apache Commons Lang：老牌工具库，提供 ArrayUtils 类。这些库通常提供了更丰富、更便捷的API，例如判空、反转、合并等，可以作为补充。⚠️ 踩坑指南Arrays.asList() 的“坑”：返回的是 java.util.Arrays.ArrayList，不是我们常用的 java.util.ArrayList，不支持 add/remove 操作，否则会抛出 UnsupportedOperationException。基本类型数组的误用：如 Arrays.asList(new int[]{1,2})，得到的是一个 List<int[]>，而非 List<Integer>。空指针异常 (NullPointerException)：当数组或集合为 null 时，直接操作或遍历会抛出 NPE。建议使用 Spring 的 CollectionUtils.isEmpty()或 ObjectUtils.isEmpty()进行安全检查。数组越界 (ArrayIndexOutOfBoundsException)：访问数组时，索引必须在 [0, array.length - 1] 范围内。遍历时优先使用增强 for 循环或 Arrays.stream() 来避免手动管理索引。💎 总结Spring Boot 2.7 企业开发中的数组操作，核心是掌握 java.util.Arrays 和 org.springframework.util 包下的工具类。对于数组与集合的转换、判空等高频操作，优先使用 Spring 的 CollectionUtils，可以避免很多常见的坑。对于复杂的业务逻辑，善用 Stream API 能让代码更清晰、更优雅。
2.1 类与对象核心概念类是对象的模板，对象是类的实例。类定义了对象的属性（成员变量）和行为（成员方法），而对象是类的具体化表现。定义语法// 定义类 [访问修饰符] class 类名 { // 成员变量（属性） 数据类型 变量名; // 成员方法（行为） 返回类型 方法名(参数列表) { // 方法体 } }实例化语法类名 对象名 = new 类名();完整示例// 定义一个学生类 public class Student { // 成员变量 String name; int age; double score; // 成员方法 public void study() { System.out.println(name + " 正在学习"); } public void introduce() { System.out.println("我叫 " + name + "，今年 " + age + " 岁，成绩是 " + score); } } // 使用类创建对象 public class Main { public static void main(String[] args) { // 实例化：创建对象 Student s1 = new Student(); Student s2 = new Student(); // 为对象赋值 s1.name = "张三"; s1.age = 18; s1.score = 92.5; s2.name = "李四"; s2.age = 19; s2.score = 88.0; // 调用对象方法 s1.study(); // 输出：张三 正在学习 s1.introduce(); // 输出：我叫 张三，今年 18 岁，成绩是 92.5 s2.introduce(); // 输出：我叫 李四，今年 19 岁，成绩是 88.0 } }内存分析栈内存：存储局部变量和对象引用（引用变量名）。堆内存：存储 new 出来的对象实体（成员变量值）。元空间：静态变量存储在堆中，类元数据（方法、字段描述等）存储在元空间Student s1 = new Student(); // s1 在栈中，new Student() 在堆中注意事项一个 Java 文件中可以有多个类，但最多只有一个 public 修饰的类，且文件名必须与 public 类名一致。对象的默认值：数值类型为 0 / 0.0，boolean 为 false，引用类型为 null。使用 new 关键字的过程：加载类 → 在堆中分配内存 → 初始化默认值 → 执行构造方法 → 返回引用地址。2.2 方法定义语法[访问修饰符] [static] [final] 返回值类型 方法名(参数类型 参数名, ...) { // 方法体 [return 返回值;] }完整示例public class Calculator { // 实例方法：需要对象调用 public int add(int a, int b) { return a + b; } // 静态方法：通过类名直接调用 public static int subtract(int a, int b) { return a - b; } // 无返回值方法 public void printResult(String operation, int result) { System.out.println(operation + " 结果：" + result); } } // 调用示例 public class Main { public static void main(String[] args) { // 静态方法：类名调用 int diff = Calculator.subtract(10, 3); // 实例方法：对象调用 Calculator calc = new Calculator(); int sum = calc.add(10, 3); calc.printResult("加法", sum); } }静态方法 vs 实例方法对比对比维度静态方法 (static)实例方法 (非static)修饰符使用 static 修饰不使用 static调用方式类名直接调用 ClassName.method()必须通过对象调用 obj.method()内存位置方法区(就是静态的代码方法而已)元空间(就是静态的代码方法而已)访问实例成员不能直接访问实例变量和实例方法可以访问所有成员（静态 + 实例）访问静态成员可以访问（推荐用类名访问）可以访问this / super不能使用可以使用应用场景工具方法（如 Math.pow()）、独立功能与对象状态相关的方法参数传递详解Java 中所有参数传递都是值传递，但理解基本类型和引用类型的区别至关重要。基本类型传递（传递副本，不改变原值）public class ParamTest { public static void changeValue(int x) { x = 100; // 修改的是副本，不影响原值 System.out.println("方法内 x = " + x); } public static void main(String[] args) { int a = 10; changeValue(a); System.out.println("方法外 a = " + a); // 输出 10，原值不变 } }引用类型传递（传递引用副本，可修改对象内容但不能改变引用指向）public class ParamTest { public static void modifyObject(StringBuilder sb) { sb.append(" World"); // 可以修改对象内容 System.out.println("方法内 sb = " + sb); } public static void changeReference(StringBuilder sb) { sb = new StringBuilder("New Object"); // 不能改变引用指向 System.out.println("方法内 sb = " + sb); } public static void main(String[] args) { StringBuilder sb = new StringBuilder("Hello"); modifyObject(sb); System.out.println("方法外 sb = " + sb); // 输出 Hello World，内容已改变 changeReference(sb); System.out.println("方法外 sb = " + sb); // 仍输出 Hello World，引用未改变 } }可变参数（varargs）// 可变参数：参数类型... 参数名（本质是数组） public static int sum(int... numbers) { int total = 0; for (int n : numbers) { total += n; } return total; } // 调用 int result = sum(1, 2, 3, 4, 5); // result = 15 int result2 = sum(); // result2 = 0注意：可变参数必须是参数列表中的最后一个参数，一个方法最多只能有一个可变参数。方法设计原则原则说明示例单一职责每个方法只做一件事，职责明确calculateTotal() 不应同时打印和计算命名清晰方法名使用动词开头，见名知义getUserById() 优于 get()参数不超过5个参数过多应封装为对象使用 DTO / POJO 封装多参数方法体不超过50行保持方法简短易读超出则拆分子方法无副作用尽量避免修改传入参数返回新值而非修改参数方法命名规范获取值：getXxx() / findXxx() / queryXxx()设置值：setXxx() / updateXxx()判断：isXxx() / hasXxx() / canXxx()转换：toXxx() / asXxx()创建：createXxx() / buildXxx() / newXxx()处理：processXxx() / handleXxx() / executeXxx()2.3 构造方法核心概念构造方法是一种特殊的方法，用于在创建对象时初始化对象的状态。构造方法特征方法名必须与类名完全相同（包括大小写）没有返回类型，连 void 也不能写使用 new 关键字自动调用，不能像普通方法一样显式调用每个类至少有一个构造方法基本用法public class Person { private String name; private int age; // 无参构造方法（默认构造方法） public Person() { System.out.println("Person 无参构造被调用"); } // 有参构造方法 public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person 有参构造被调用，name = " + name); } // 多个重载的构造方法 public Person(String name) { this.name = name; this.age = 0; // 默认值 } }重要规则一旦手动定义了有参构造方法，编译器将不再自动提供默认的无参构造方法。如果需要无参构造，必须显式定义。public class Student { private String name; // 只定义了有参构造 public Student(String name) { this.name = name; } } // 以下代码将编译错误！ // Student s = new Student(); // 错误：无参构造不存在链式调用 this()this() 用于在一个构造方法中调用本类的另一个构造方法，实现代码复用。public class Employee { private String name; private int age; private String department; // 构造方法1：全参 public Employee(String name, int age, String department) { this.name = name; this.age = age; this.department = department; } // 构造方法2：只有姓名和年龄 public Employee(String name, int age) { this(name, age, "未分配"); // 调用构造方法1 } // 构造方法3：只有姓名 public Employee(String name) { this(name, 0, "未分配"); // 调用构造方法1 } // 构造方法4：无参 public Employee() { this("未知", 0, "未分配"); // 调用构造方法1 } }this() 使用规则this() 必须是构造方法的第一条语句。一个构造方法中最多只能出现一次 this() 调用。不能形成循环调用，否则编译错误。// 错误示例：循环调用 public class A { public A() { this(10); // 调用有参构造 } public A(int x) { this(); // 调用无参构造 → 形成循环，编译错误！ } }Builder 模式当类的属性过多时，构造方法的参数列表会变得冗长且难以阅读。Builder 模式是解决此问题的优雅方案。public class Computer { // 必选参数 private final String cpu; private final String ram; // 可选参数 private final String gpu; private final String storage; private final String os; // 私有构造方法，仅 Builder 可调用 private Computer(Builder builder) { this.cpu = builder.cpu; this.ram = builder.ram; this.gpu = builder.gpu; this.storage = builder.storage; this.os = builder.os; } // 静态内部 Builder 类 public static class Builder { // 必选参数 private final String cpu; private final String ram; // 可选参数（默认值） private String gpu = "集成显卡"; private String storage = "256GB SSD"; private String os = "Windows 11"; // Builder 构造方法：必须提供必选参数 public Builder(String cpu, String ram) { this.cpu = cpu; this.ram = ram; } public Builder gpu(String gpu) { this.gpu = gpu; return this; } public Builder storage(String storage) { this.storage = storage; return this; } public Builder os(String os) { this.os = os; return this; } public Computer build() { return new Computer(this); } } @Override public String toString() { return "Computer {cpu=" + cpu + ", ram=" + ram + ", gpu=" + gpu + ", storage=" + storage + ", os=" + os + "}"; } } // 使用 Builder 模式创建对象 public class Main { public static void main(String[] args) { Computer pc1 = new Computer.Builder("Intel i7", "16GB") .build(); Computer pc2 = new Computer.Builder("AMD Ryzen 9", "32GB") .gpu("NVIDIA RTX 4080") .storage("1TB NVMe SSD") .os("Linux") .build(); System.out.println(pc1); System.out.println(pc2); } }构造方法设计原则原则说明参数验证在构造方法中对参数进行合法性校验，确保对象创建后处于有效状态链式调用利用 this() 减少重复代码，保持构造逻辑集中防御性复制对于可变引用类型参数，创建防御性副本，防止外部修改影响内部状态保持简洁构造方法应只做初始化工作，避免复杂业务逻辑不调用可重写方法构造方法中调用可被重写的方法会导致不可预期的行为（子类可能尚未初始化完毕）// 参数验证 + 防御性复制示例 public class Order { private final List<String> items; private final Date createTime; public Order(List<String> items) { // 参数验证 if (items == null || items.isEmpty()) { throw new IllegalArgumentException("订单项不能为空"); } // 防御性复制 this.items = new ArrayList<>(items); this.createTime = new Date(); } public List<String> getItems() { // 返回防御性副本 return new ArrayList<>(items); } }2.4 访问修饰符四种访问级别Java 提供四种访问修饰符来控制类、成员变量、成员方法和构造方法的可见性。修饰符同类同包子类（不同包）所有类private可访问不可访问不可访问不可访问default（无修饰符）可访问可访问不可访问不可访问protected可访问可访问可访问不可访问public可访问可访问可访问可访问代码示例package com.example.base; public class Parent { private int privateField = 1; // 仅本类内访问 int defaultField = 2; // 同包访问（default） protected int protectedField = 3; // 同包 + 子类访问 public int publicField = 4; // 所有类访问 public void testAccess() { System.out.println(privateField); // 可访问（同类内） System.out.println(defaultField); // 可访问 System.out.println(protectedField); // 可访问 System.out.println(publicField); // 可访问 } }package com.example.base; public class SamePackageClass { public void testAccess() { Parent p = new Parent(); // System.out.println(p.privateField); // 编译错误：不同类 System.out.println(p.defaultField); // 可访问（同包） System.out.println(p.protectedField); // 可访问（同包） System.out.println(p.publicField); // 可访问 } }package com.example.other; import com.example.base.Parent; public class Child extends Parent { public void testAccess() { // System.out.println(privateField); // 编译错误 // System.out.println(defaultField); // 编译错误：不同包 System.out.println(protectedField); // 可访问（子类） System.out.println(publicField); // 可访问 } }package com.example.other; import com.example.base.Parent; public class UnrelatedClass { public void testAccess() { Parent p = new Parent(); // System.out.println(p.privateField); // 编译错误 // System.out.println(p.defaultField); // 编译错误 // System.out.println(p.protectedField);// 编译错误：不同包非子类 System.out.println(p.publicField); // 可访问 } }访问修饰符可修饰的元素修饰的元素privatedefaultprotectedpublic外部类不支持支持不支持支持内部类支持支持支持支持成员变量支持支持支持支持成员方法支持支持支持支持构造方法支持支持支持支持设计原则封装核心：隐藏内部实现细节，只暴露必要的公共接口。最小权限原则：能用 private 就不用 default，能用 default 就不用 protected，能用 protected 就不用 public。字段私有：所有成员变量应使用 private 修饰，通过 getter/setter 方法访问。常量公开：公共常量使用 public static final。内部辅助方法：仅供类内部使用的方法使用 private。// 良好的封装示例 public class BankAccount { private String accountNumber; // 私有的内部数据 private double balance; // 公共的访问接口 public String getAccountNumber() { return accountNumber; } public double getBalance() { return balance; } // 存款：公共方法，但内部有验证 public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("存款金额必须大于0"); } balance += amount; } // 内部使用的私有辅助方法 private void validateAmount(double amount) { if (amount <= 0) { throw new IllegalArgumentException("金额无效"); } } }2.5 this 关键字核心概念this 是 Java 中的关键字，表示对当前对象的引用。每个实例方法中都有一个隐含的 this 引用指向调用该方法的对象。三大用途1. 引用成员变量（区分同名的局部变量和成员变量）当方法参数名与成员变量名相同时，必须使用 this 来区分。public class Person { private String name; private int age; // 必须使用 this 区分 public Person(String name, int age) { this.name = name; // this.name 是成员变量，name 是参数 this.age = age; // this.age 是成员变量，age 是参数 } public void setName(String name) { this.name = name; // 参数与成员变量同名时必须使用 this } // 不同名时可以省略 this（但不推荐，降低可读性） public void setAge(int newAge) { age = newAge; // 可以省略 this // this.age = newAge; // 推荐显式使用 this } }2. 调用本类的其他构造方法 this()public class Student { private String name; private int age; private String grade; public Student() { this("未知", 0, "未分配"); // 调用三参构造方法 } public Student(String name) { this(name, 0, "未分配"); // 调用三参构造方法 } public Student(String name, int age) { this(name, age, "未分配"); // 调用三参构造方法 } // 最终的构造方法（入口） public Student(String name, int age, String grade) { this.name = name; this.age = age; this.grade = grade; } }3. 方法链式调用（Fluent API）返回 this 可以实现链式调用，使代码更简洁流畅。public class QueryBuilder { private String table; private List<String> columns = new ArrayList<>(); private String whereClause; private String orderBy; public QueryBuilder select(String... cols) { columns.addAll(Arrays.asList(cols)); return this; // 返回当前对象，支持链式调用 } public QueryBuilder from(String table) { this.table = table; return this; } public QueryBuilder where(String condition) { this.whereClause = condition; return this; } public QueryBuilder orderBy(String field) { this.orderBy = field; return this; } public String build() { // 构建 SQL 语句... return "SELECT " + String.join(", ", columns) + " FROM " + table + (whereClause != null ? " WHERE " + whereClause : "") + (orderBy != null ? " ORDER BY " + orderBy : ""); } // 使用示例 public static void main(String[] args) { QueryBuilder qb = new QueryBuilder(); String sql = qb.select("id", "name", "age") .from("users") .where("age > 18") .orderBy("name") .build(); System.out.println(sql); // 输出：SELECT id, name, age FROM users WHERE age > 18 ORDER BY name } }this 使用限制限制说明示例静态方法中不能使用静态方法属于类，没有对象上下文，不存在 thisstatic void method() { this.xxx; } ❌静态代码块中不能使用静态代码块在类加载时执行，此时没有对象实例static { this.xxx; } ❌this() 必须是构造方法第一条语句构造方法调用必须在最前面public A() { this("default"); }使用场景总结场景是否使用 this说明成员变量与局部变量同名必须使用this.name = name;成员变量与局部变量不同名推荐使用提高可读性，明确意图构造方法间相互调用必须使用 this()第一条语句链式调用必须使用 return thisFluent API 模式将当前对象作为参数传递使用 thismethod(this)静态上下文中禁止使用编译错误2.6 static 关键字核心概念static 表示"静态的"、"属于类的"，被 static 修饰的成员不属于任何一个对象实例，而是属于类本身。静态变量（类变量）静态变量属于类，被该类的所有对象共享，其生命周期与类的生命周期一致。public class Counter { // 静态变量：所有 Counter 对象共享 private static int totalCount = 0; // 实例变量：每个对象独立拥有 private int instanceId; public Counter() { totalCount++; // 每次创建对象，计数 +1 instanceId = totalCount; // 将总数作为当前ID } public static int getTotalCount() { return totalCount; } public int getInstanceId() { return instanceId; } public static void main(String[] args) { Counter c1 = new Counter(); Counter c2 = new Counter(); Counter c3 = new Counter(); System.out.println(c1.getInstanceId()); // 输出: 1 System.out.println(c2.getInstanceId()); // 输出: 2 System.out.println(c3.getInstanceId()); // 输出: 3 System.out.println(Counter.getTotalCount());// 输出: 3（所有对象共享） // 三个对象共享同一个 totalCount System.out.println(c1.getTotalCount()); // 输出: 3 System.out.println(c2.getTotalCount()); // 输出: 3 } }静态变量的内存与生命周期阶段说明加载类被 JVM 加载时，静态变量在方法区分配空间并初始化使用可通过 类名.变量名 或 对象.变量名 访问（推荐类名方式）卸载类被卸载时，静态变量占用的内存释放静态方法（重点）类名.静态变量变量引用在堆（附着于Class对象）类加载时初始化，类卸载（或JVM退出）时销毁类名.静态方法()代码在元空间，执行时的临时数据在栈类加载后即可调用，调用结束栈帧即销毁，随类卸载而消失静态方法属于类，只能直接访问静态成员（静态变量和静态方法），不能直接访问实例成员。public class MathUtils { // 静态常量 public static final double PI = 3.1415926535; // 静态方法：不依赖对象状态 public static double squareArea(double side) { return side * side; } public static double circleArea(double radius) { return PI * radius * radius; } // 实例方法：可以访问静态成员 public void printPI() { System.out.println("PI = " + PI); // 实例方法访问静态变量，OK System.out.println("面积 = " + squareArea(5)); // OK } // 错误示例：静态方法访问实例成员 // public static void errorMethod() { // printPI(); // 编译错误！静态方法不能调用实例方法 // } } // 使用 public class Main { public static void main(String[] args) { // 直接通过类名调用 double area = MathUtils.circleArea(5.0); System.out.println("圆面积：" + area); } }静态方法不能被重写，只能被隐藏class Parent { public static void staticMethod() { System.out.println("Parent 静态方法"); } public void instanceMethod() { System.out.println("Parent 实例方法"); } } class Child extends Parent { // 这不是重写(Override)，而是隐藏(Hide) public static void staticMethod() { System.out.println("Child 静态方法"); } // 这是真正的重写 @Override public void instanceMethod() { System.out.println("Child 实例方法"); } } public class Test { public static void main(String[] args) { Parent p = new Child(); p.staticMethod(); // 输出：Parent 静态方法（基于引用类型，不具有多态性） p.instanceMethod(); // 输出：Child 实例方法（基于实际对象类型，多态性） Child.staticMethod(); // 输出：Child 静态方法（调用子类的隐藏版本） } }静态代码块静态代码块在类加载时执行，且只执行一次，常用于初始化静态资源。public class DatabaseConfig { // 静态变量 private static String url; private static String username; private static String password; // 静态代码块：类加载时只执行一次 static { System.out.println("正在加载数据库配置..."); // 模拟从配置文件加载 url = "jdbc:mysql://localhost:3306/mydb"; username = "root"; password = "123456"; System.out.println("数据库配置加载完成"); } // 静态代码块可以有多个，按声明顺序执行 static { System.out.println("第二个静态代码块执行"); } public static void connect() { System.out.println("连接数据库：" + url); } public static void main(String[] args) { DatabaseConfig.connect(); // 输出： // 正在加载数据库配置... // 数据库配置加载完成 // 第二个静态代码块执行 // 连接数据库：jdbc:mysql://localhost:3306/mydb } }初始化顺序总结当类被加载和实例化时，各部分的初始化顺序如下：1. 父类静态变量和静态代码块（按声明顺序） 2. 子类静态变量和静态代码块（按声明顺序） 3. 父类实例变量和实例代码块（按声明顺序） 4. 父类构造方法 5. 子类实例变量和实例代码块（按声明顺序） 6. 子类构造方法class Parent { static { System.out.println("1. Parent 静态代码块"); } { System.out.println("3. Parent 实例代码块"); } Parent() { System.out.println("4. Parent 构造方法"); } } class Child extends Parent { static { System.out.println("2. Child 静态代码块"); } { System.out.println("5. Child 实例代码块"); } Child() { System.out.println("6. Child 构造方法"); } public static void main(String[] args) { new Child(); } }2.7 继承核心概念继承是面向对象编程的核心特性之一，允许一个类（子类）继承另一个类（父类）的属性和方法。基本语法[访问修饰符] class 子类名 extends 父类名 { // 子类特有的属性和方法 }基本示例// 父类（基类 / 超类） public class Animal { protected String name; protected int age; public Animal(String name, int age) { this.name = name; this.age = age; } public void eat() { System.out.println(name + " 正在吃东西"); } public void sleep() { System.out.println(name + " 正在睡觉"); } } // 子类（派生类） public class Dog extends Animal { private String breed; public Dog(String name, int age, String breed) { super(name, age); // 调用父类构造方法 this.breed = breed; } // 子类特有方法 public void bark() { System.out.println(name + " 汪汪叫！"); } // 重写父类方法 @Override public void eat() { System.out.println(name + "（" + breed + "）正在吃狗粮"); } } // Cat 子类 public class Cat extends Animal { public Cat(String name, int age) { super(name, age); } @Override public void eat() { System.out.println(name + " 正在吃鱼"); } public void meow() { System.out.println(name + " 喵喵叫！"); } } // 使用 public class Main { public static void main(String[] args) { Dog dog = new Dog("旺财", 3, "金毛"); Cat cat = new Cat("咪咪", 2); dog.eat(); // 输出：旺财（金毛）正在吃狗粮 dog.bark(); // 输出：旺财 汪汪叫！ dog.sleep(); // 输出：旺财 正在睡觉（继承自 Animal） cat.eat(); // 输出：咪咪 正在吃鱼 cat.meow(); // 输出：咪咪 喵喵叫！ cat.sleep(); // 输出：咪咪 正在睡觉（继承自 Animal） } }Java 继承规则规则说明单继承一个类只能有一个直接父类（extends 后只能跟一个类名）多层继承可以形成继承链：A extends B, B extends C（层次通常不超过 3-4 层）隐式继承 Object所有类如果没有显式继承，则隐式继承 java.lang.Object不继承构造方法子类不继承父类的构造方法，但可以通过 super() 调用继承所有非私有成员子类继承父类的 public、protected、default 成员（同包情况下）is-a 关系判断继承表示 "is-a"（是一个）关系。判断是否适合使用继承的关键标准：// 好的继承：Dog IS-A Animal — 合理 class Dog extends Animal { } // 狗是一种动物 // 好的继承：Car IS-A Vehicle — 合理 class Car extends Vehicle { } // 汽车是一种交通工具 // 不好的继承：为了复用代码而继承 class Stack extends ArrayList { } // 栈不是一个列表，违反了 is-a // 应该使用组合： class Stack { private ArrayList list = new ArrayList(); // 组合优于继承 }继承的设计原则推荐做法// 1. 父类设计考虑扩展性，使用 protected 提供子类访问 public class BaseService { protected Logger logger = LoggerFactory.getLogger(getClass()); // 模板方法模式：定义框架，子类实现细节 public final void execute() { beforeExecute(); doExecute(); afterExecute(); } protected void beforeExecute() { } // 钩子方法 protected abstract void doExecute(); // 子类必须实现 protected void afterExecute() { } // 钩子方法 } // 2. 使用 @Override 注解防止错误 class ChildService extends BaseService { @Override // 编译器会检查是否正确重写，防止拼写错误 protected void doExecute() { logger.info("执行业务逻辑"); } } // 3. 子类构造调用 super() class Child extends Parent { public Child(String name) { super(name); // 必须调用父类构造方法，确保父类正确初始化 } }避免的做法避免的做法原因正确做法为代码复用强行继承违反 is-a 原则，导致耦合使用组合 + 委托在构造方法中调用可重写方法子类方法可能在父类未完全初始化时被调用构造方法只调用 private / final / static 方法随意修改父类违反开闭原则通过扩展（新增子类）而非修改父类来增加功能// 违反开闭原则的反例 // 不应该为新增功能而修改稳定的父类 // 正确做法：使用组合替代不当的继承 // 需要复用某功能，但不符合 is-a 关系时 class ReportGenerator { // 组合：持有需要的对象 private DataFetcher dataFetcher; private Formatter formatter; public ReportGenerator(DataFetcher dataFetcher, Formatter formatter) { this.dataFetcher = dataFetcher; this.formatter = formatter; } }2.8 super 关键字核心概念super 是 Java 中指向直接父类对象引用的关键字，用于在子类中访问父类的成员（构造方法、方法、属性）。三大用途1. 调用父类构造方法 super()class Parent { protected String name; // 父类有参构造 public Parent(String name) { this.name = name; System.out.println("Parent 构造：name = " + name); } } class Child extends Parent { private int age; public Child(String name, int age) { super(name); // 必须显式调用父类有参构造 this.age = age; System.out.println("Child 构造：age = " + age); } } public class Main { public static void main(String[] args) { Child c = new Child("小明", 10); // 输出： // Parent 构造：name = 小明 // Child 构造：age = 10 } }2. 访问父类被重写的方法class Animal { public void makeSound() { System.out.println("动物发出声音"); } } class Dog extends Animal { @Override public void makeSound() { super.makeSound(); // 调用父类的方法 System.out.println("狗：汪汪！"); // 扩展父类方法 } } // 使用 public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.makeSound(); // 输出： // 动物发出声音 // 狗：汪汪！ } }3. 访问父类被隐藏的属性class Parent { protected String className = "Parent"; protected int value = 100; } class Child extends Parent { protected String className = "Child"; // 隐藏父类同名属性 public void printInfo() { System.out.println("子类的 className: " + this.className); // Child System.out.println("父类的 className: " + super.className); // Parent System.out.println("value: " + super.value); // 100（继承的） } }super 使用规则规则说明super() 必须是第一条语句super() 调用必须放在构造方法体的第一行不显式调用自动插入如果子类构造方法没有显式调用 super()，编译器会自动插入父类的无参构造 super()父类无无参构造如果父类没有无参构造方法，子类必须显式通过 super(参数) 调用父类的有参构造，否则编译错误不能在静态方法中使用super 是对象级别的引用，静态上下文中不存在// 错误示例：父类无无参构造但子类未显式调用 class Parent { public Parent(int x) { } // 只有有参构造 } // class Child extends Parent { // // 编译错误！隐式的 super() 找不到 Parent 的无参构造 // } // 正确示例 class Child extends Parent { public Child(int x) { super(x); // 必须显式调用 } }super vs this 对比对比维度superthis访问范围访问父类的成员（方法、属性）访问当前类的成员（方法、属性）调用构造方法super() 调用父类的构造方法this() 调用本类的其他构造方法本质对直接父类的引用对当前对象的引用内存指向指向父类部分的内存区域指向当前对象整体静态方法中不能使用不能使用第一条语句限制super() 必须是构造方法第一条语句this() 必须是构造方法第一条语句能否共存不能与 this() 同时作为第一条语句不能与 super() 同时作为第一条语句2.9 final 关键字核心概念final 表示"最终的、不可改变的"，可以修饰类、方法和变量。final 类：不能被继承当一个类被 final 修饰时，它不能被任何类继承。// final 类：不可被继承 public final class StringUtils { public static boolean isEmpty(String str) { return str == null || str.length() == 0; } } // 以下代码编译错误！ // class ExtendedStringUtils extends StringUtils { } // 编译错误！典型 final 类：java.lang.String、java.lang.Math、java.lang.Integer 等包装类。final 方法：不能被重写，但能被继承调用class Parent { // final 方法：子类不能重写 public final void criticalMethod() { System.out.println("核心方法，不能被子类改变"); } // 普通方法：可以被子类重写 public void normalMethod() { System.out.println("普通方法"); } } class Child extends Parent { // @Override // public void criticalMethod() { } // 编译错误！final 方法不能重写 @Override public void normalMethod() { System.out.println("子类重写的普通方法"); } // 可以继承并调用父类的 final 方法 public void useParentMethod() { criticalMethod(); // 调用继承的 final 方法，OK } }final 变量：赋值后不能修改final 变量根据声明位置的不同，初始化时机也有所不同。public class FinalVariableDemo { // 1. 静态 final 变量（类常量）：声明时或静态代码块中初始化 public static final double PI = 3.14159; public static final String APP_NAME; static { APP_NAME = "MyApplication"; // 静态代码块中初始化 } // 2. 实例 final 变量：声明时或构造方法中初始化 private final String id; // 声明时未赋值 private final String name = "默认名称"; // 声明时赋值 public FinalVariableDemo(String id) { this.id = id; // 构造方法中初始化 } // 3. 局部 final 变量：使用前初始化一次 public void process() { final int localValue; localValue = 100; // 首次赋值，OK // localValue = 200; // 编译错误！不能二次赋值 } // final 引用类型：引用不可变，但对象内容可变 public void finalReference() { final StringBuilder sb = new StringBuilder("Hello"); sb.append(" World"); // OK：可以修改对象内容 // sb = new StringBuilder("New"); // 编译错误！不能改变引用指向 } }final 变量初始化规则总结final 变量类型必须初始化的时机示例静态 final 变量声明时 或 静态代码块中static final int X = 10; 或 static { X = 10; }实例 final 变量声明时 或 每个构造方法中final int x = 10; 或构造方法中 this.x = 10;局部 final 变量使用前赋值一次即可final int x; x = 10; use(x);final 方法参数public class FinalParameter { // final 参数：方法调用时赋值，方法体内不可修改 public void printName(final String name) { // name = "新的名字"; // 编译错误！不能修改 final 参数 System.out.println("姓名：" + name); } // final 参数在匿名内部类中很有用 public void doSomething(final String message) { Runnable r = new Runnable() { @Override public void run() { System.out.println(message); // 匿名内部类可以访问 final 变量 } }; new Thread(r).start(); } }final 使用场景场景说明示例定义常量public static final 定义公共常量Math.PI防止继承工具类、不可变类String、Integer防止重写模板方法中的框架方法流程控制方法安全发布确保对象在构造函数中完全初始化多线程环境下的不可变对象匿名内部类匿名内部类访问的局部变量final 局部变量在 Lambda 中2.10 方法重写与重载方法重写（Override）子类重新定义从父类继承的方法，提供自己的实现。class Animal { public void makeSound() { System.out.println("动物发出声音"); } } class Dog extends Animal { @Override // 注解标记，编译器会检查是否正确重写 public void makeSound() { System.out.println("狗汪汪叫"); } } class Cat extends Animal { @Override public void makeSound() { System.out.println("猫喵喵叫"); } }重写规则（五同两小一大）规则说明示例方法名相同子类方法名必须与父类完全一致makeSound() = makeSound()参数相同参数列表（类型、数量、顺序）必须完全一致void eat(String food) = void eat(String food)返回类型相同或子类型Java 5+ 支持协变返回类型父类返回 Animal，子类可返回 Dog（Dog extends Animal）访问修饰符不能更严格只能扩大，不能缩小父类 protected → 子类 public（OK）；父类 public → 子类 protected（NO）不能抛出更宽泛异常只能缩小或相同父类抛 Exception → 子类可抛 IOException（更具体）// 协变返回类型示例 class Animal { public Animal reproduce() { return new Animal(); } } class Dog extends Animal { @Override public Dog reproduce() { return new Dog(); } // 返回子类型，OK }不能被重写的情况类型原因final 方法被 final 锁定，不可改变static 方法属于类，不能重写（只能隐藏）private 方法子类不可见，谈不上重写构造方法不在继承范围内方法重载（Overload）在同一个类中，定义多个同名但参数列表不同的方法。public class Calculator { // 重载：参数数量不同 public int add(int a, int b) { return a + b; } public int add(int a, int b, int c) { return a + b + c; } // 重载：参数类型不同 public double add(double a, double b) { return a + b; } // 重载：参数顺序不同（类型不同） public String add(String prefix, int value) { return prefix + value; } public String add(int value, String suffix) { return value + suffix; } // 错误示例：只有返回类型不同，不是重载 // public long add(int a, int b) { return a + b; } // 编译错误！ }重载的选择规则编译器根据调用时传入的实参类型和数量来决定调用哪个重载方法：public class OverloadResolution { public void test(int x) { System.out.println("int 版本：" + x); } public void test(double x) { System.out.println("double 版本：" + x); } public void test(String x) { System.out.println("String 版本：" + x); } public static void main(String[] args) { OverloadResolution demo = new OverloadResolution(); demo.test(10); // int 版本 demo.test(10.5); // double 版本 demo.test("hello"); // String 版本 // 自动类型提升 demo.test('A'); // char → int，调用 int 版本 } }重写(Override) vs 重载(Overload) 对比对比维度重写 (Override)重载 (Overload)发生位置父子类之间同一个类中方法签名方法名 + 参数列表完全一致方法名相同，参数列表不同返回类型相同或协变子类型可以不同访问修饰符不能比父类更严格可以任意异常不能比父类更宽泛可以任意多态类型运行时多态（动态绑定）编译时多态（静态绑定）注解使用 @Override 标注无需特殊注解static 方法不能重写（只能隐藏）可以重载final 方法不能重写可以重载构造方法不能重写可以重载2.11 多态核心概念多态（Polymorphism）是指同一个行为具有多个不同的表现形式。在 Java 中，多态主要通过父类引用指向子类对象来实现。多态实现的三个必要条件1. 继承关系（extends） 2. 方法重写（@Override） 3. 向上转型（父类引用指向子类对象）基本示例// 1. 定义父类 abstract class Shape { public abstract double getArea(); public void describe() { System.out.println("这是一个图形，面积为：" + getArea()); } } // 2. 子类重写方法 class Circle extends Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public double getArea() { return Math.PI * radius * radius; } } class Rectangle extends Shape { private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public double getArea() { return width * height; } } // 3. 向上转型 + 多态调用 public class PolymorphismDemo { public static void main(String[] args) { // 向上转型：父类引用指向子类对象 Shape s1 = new Circle(5.0); Shape s2 = new Rectangle(4.0, 6.0); // 多态调用：运行时根据实际对象类型调用对应方法 s1.describe(); // 输出：这是一个图形，面积为：78.5398... s2.describe(); // 输出：这是一个图形，面积为：24.0 // 使用数组或集合统一处理 Shape[] shapes = { new Circle(3.0), new Rectangle(2.0, 5.0), new Circle(4.0) }; double totalArea = 0; for (Shape s : shapes) { totalArea += s.getArea(); // 多态调用 } System.out.println("总面积：" + totalArea); } }编译时多态 vs 运行时多态类型别称实现机制决定时机典型场景编译时多态静态多态方法重载（Overload）编译期确定同名不同参的方法运行时多态动态多态方法重写（Override）+ 动态绑定运行期确定父类引用调用子类重写方法向上转型（Upcasting）向上转型是自动的、安全的。父类引用只能调用父类中定义的方法，不能调用子类特有的方法。class Animal { public void eat() { System.out.println("吃东西"); } } class Dog extends Animal { public void eat() { System.out.println("吃骨头"); } public void bark() { System.out.println("汪汪叫"); } } public class UpcastingDemo { public static void main(String[] args) { Animal a = new Dog(); // 向上转型（自动） a.eat(); // 输出：吃骨头（多态，调用 Dog 的 eat） // a.bark(); // 编译错误！Animal 引用看不到 Dog 特有方法 } }向下转型（Downcasting）向下转型需要显式强制转换，且必须先用 instanceof 检查类型，否则可能导致 ClassCastException。public class DowncastingDemo { public static void main(String[] args) { Animal a = new Dog(); // 实际是 Dog 对象 // 向下转型：必须显式转换 if (a instanceof Dog) { Dog d = (Dog) a; // 安全的向下转型 d.bark(); // 输出：汪汪叫 } // 错误示例：不安全的向下转型 Animal a2 = new Animal(); // Dog d2 = (Dog) a2; // 运行时抛出 ClassCastException！ // 安全做法：始终先检查 if (a2 instanceof Dog) { Dog d2 = (Dog) a2; d2.bark(); } else { System.out.println("a2 不是 Dog 类型"); } } }instanceof 关键字instanceof 用于判断一个对象是否是指定类型（或其子类型）的实例。public class InstanceofDemo { public static void main(String[] args) { Object obj1 = "Hello World"; Object obj2 = 42; Object obj3 = new ArrayList<>(); Object obj4 = null; System.out.println(obj1 instanceof String); // true System.out.println(obj1 instanceof Object); // true（String 是 Object 的子类） System.out.println(obj2 instanceof Integer); // true System.out.println(obj3 instanceof List); // true（ArrayList 实现了 List） System.out.println(obj4 instanceof String); // false（null 不是任何类型的实例） // 模式匹配（Java 16+） if (obj1 instanceof String s && s.length() > 0) { System.out.println("字符串长度为：" + s.length()); // 直接在条件中使用 s } } }多态的注意事项注意点说明静态方法不支持多态静态方法调用基于引用类型，不是实际对象类型私有方法不能被重写因此不存在多态构造方法不支持多态构造方法不是通过常规方法调用机制执行的成员变量没有多态成员变量访问看引用类型，方法调用看实际对象类型class Parent { public String name = "Parent"; public static void staticMethod() { System.out.println("Parent static"); } public void instanceMethod() { System.out.println("Parent instance"); } } class Child extends Parent { public String name = "Child"; // 隐藏父类变量 public static void staticMethod() { System.out.println("Child static"); } @Override public void instanceMethod() { System.out.println("Child instance"); } } public class PolymorphismTrap { public static void main(String[] args) { Parent p = new Child(); System.out.println(p.name); // 输出：Parent（变量看引用类型） p.staticMethod(); // 输出：Parent static（静态方法看引用类型） p.instanceMethod(); // 输出：Child instance（实例方法看实际类型） } }2.12 封装核心概念封装（Encapsulation）是将数据（属性）和操作数据的方法绑定在一起，隐藏内部实现细节，只暴露必要的接口给外部使用。封装的基本原则public class BankAccount { // 1. 所有字段设为 private private String accountNumber; private String ownerName; private double balance; private String password; // 2. 提供公共的 getter/setter 方法 public String getAccountNumber() { return accountNumber; } public String getOwnerName() { return ownerName; } public void setOwnerName(String ownerName) { if (ownerName == null || ownerName.trim().isEmpty()) { throw new IllegalArgumentException("所有者姓名不能为空"); } this.ownerName = ownerName; } public double getBalance() { return balance; } // 3. 不直接暴露 setter，而是提供业务方法 public void deposit(double amount) { if (amount <= 0) { throw new IllegalArgumentException("存款金额必须大于0"); } this.balance += amount; logTransaction("存款", amount); } public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException("取款金额必须大于0"); } if (amount > this.balance) { throw new IllegalStateException("余额不足"); } this.balance -= amount; logTransaction("取款", amount); } // 4. 内部辅助方法设为 private private void logTransaction(String type, double amount) { System.out.println("[交易记录] " + type + ": " + amount + " | 余额: " + balance); } } // 外部使用 public class Main { public static void main(String[] args) { BankAccount account = new BankAccount(); // account.balance = 100000; // 编译错误：balance 是 private // account.password = "123"; // 编译错误：password 是 private account.deposit(1000); // 通过公共方法操作 account.withdraw(300); System.out.println("余额：" + account.getBalance()); // 700.0 } }封装的四个层次访问控制逐级放松： ┌────────────────────────────────────────────┐ │ private 同类内 │ │ ┌──────────────────────────────────────┐ │ │ │ default 同包内 │ │ │ │ ┌────────────────────────────────┐ │ │ │ │ │ protected 同包 + 子类 │ │ │ │ │ │ ┌──────────────────────────┐ │ │ │ │ │ │ │ public 所有类均可访问 │ │ │ │ │ │ │ └──────────────────────────┘ │ │ │ │ │ └────────────────────────────────┘ │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────────┘封装的好处好处说明示例隐藏实现细节外部只需知道"做什么"，无需知道"怎么做"调用 deposit() 不知内部如何记账数据验证在 setter 或业务方法中统一校验防止余额出现负数灵活修改内部实现改变不影响外部调用者改用数据库存储，外部调用代码不变提高安全性防止外部直接篡改内部数据密码字段不暴露 getter降低耦合模块间通过接口通信便于单元测试和维护封装最佳实践public class Person { // 所有字段 private private String name; private int age; private String email; // 构造方法中进行参数验证 public Person(String name, int age, String email) { setName(name); // 复用验证逻辑 setAge(age); setEmail(email); } // getter：只读属性 public String getName() { return name; } public int getAge() { return age; } // setter：包含验证逻辑 public void setName(String name) { if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("姓名不能为空"); } this.name = name; } public void setAge(int age) { if (age < 0 || age > 150) { throw new IllegalArgumentException("年龄必须在 0-150 之间"); } this.age = age; } public void setEmail(String email) { if (email != null && !email.contains("@")) { throw new IllegalArgumentException("邮箱格式不正确"); } this.email = email; } // 需要时才暴露 getter（最小化暴露面） // email 不提供 getter，只有需要发送邮件时才内部使用 }2.13 抽象类与接口抽象类（Abstract Class）使用 abstract 关键字修饰的类。抽象类不能实例化，通常作为其他类的基类。// 抽象类 public abstract class Animal { // 成员变量 protected String name; // 构造方法（抽象类可以有构造方法） public Animal(String name) { this.name = name; } // 抽象方法：没有方法体，子类必须实现 public abstract void makeSound(); // 具体方法：有方法体，可以被子类继承和使用 public void eat() { System.out.println(name + " 正在吃东西"); } public void sleep() { System.out.println(name + " 正在睡觉"); } } // 子类实现抽象类 public class Dog extends Animal { public Dog(String name) { super(name); } // 必须重写（实现）所有抽象方法 @Override public void makeSound() { System.out.println(name + " 汪汪叫！"); } } public class Cat extends Animal { public Cat(String name) { super(name); } @Override public void makeSound() { System.out.println(name + " 喵喵叫！"); } } public class Main { public static void main(String[] args) { // Animal a = new Animal("xx"); // 编译错误！抽象类不能实例化 Animal dog = new Dog("旺财"); Animal cat = new Cat("咪咪"); dog.makeSound(); // 旺财 汪汪叫！ dog.eat(); // 旺财 正在吃东西 cat.makeSound(); // 咪咪 喵喵叫！ cat.eat(); // 咪咪 正在吃东西 } }抽象类特点特点说明abstract 修饰必须使用 abstract 关键字声明不能实例化new Animal() 编译错误可有构造方法供子类通过 super() 调用可有抽象方法没有方法体，abstract 修饰，子类必须实现可有具体方法有方法体的普通方法可有成员变量可以有实例变量和各种修饰符的变量单继承一个类只能继承一个抽象类接口（Interface）接口是一种更纯粹的抽象类型，JDK 8+ 支持 default 和 static 方法。// 定义接口 public interface Flyable { // 常量：默认 public static final int MAX_SPEED = 1000; String TYPE = "飞行器"; // 抽象方法：默认 public abstract void fly(); void land(); // Java 8+：default 方法（有默认实现） default void takeOff() { System.out.println("准备起飞..."); checkEngine(); } // Java 8+：static 方法 static String getFlyableType() { return "这是一个可飞行类型"; } // Java 9+：private 方法（供 default 方法内部复用） private void checkEngine() { System.out.println("检查引擎状态..."); } } // 另一个接口 public interface Swimmable { void swim(); default void dive() { System.out.println("潜入水中..."); } } // 一个类可以实现多个接口 public class Duck implements Flyable, Swimmable { private String name; public Duck(String name) { this.name = name; } @Override public void fly() { System.out.println(name + " 在飞行"); } @Override public void land() { System.out.println(name + " 降落在水面"); } @Override public void swim() { System.out.println(name + " 在游泳"); } } // 接口使用 public class Main { public static void main(String[] args) { Duck duck = new Duck("唐老鸭"); duck.takeOff(); // 调用接口 default 方法 duck.fly(); duck.land(); duck.swim(); duck.dive(); // 调用接口 default 方法 // 访问接口常量 System.out.println("最大速度：" + Flyable.MAX_SPEED); // 调用接口 static 方法 System.out.println(Flyable.getFlyableType()); } }接口特点特点说明interface 声明使用 interface 关键字，非 class不能实例化new Flyable() 编译错误无构造方法接口没有构造方法所有变量默认 public static final必须是常量方法默认 public abstractJDK 8 前所有方法都是抽象方法JDK 8+ default 方法有方法体的默认方法（可被重写）JDK 8+ static 方法接口的静态工具方法JDK 9+ private 方法接口内部的私有辅助方法多实现一个类可以实现多个接口接口的多实现和冲突解决interface A { default void hello() { System.out.println("Hello from A"); } } interface B { default void hello() { System.out.println("Hello from B"); } } // 当多个接口有同名 default 方法时，必须重写解决冲突 class C implements A, B { @Override public void hello() { // 选择调用某一个接口的实现 A.super.hello(); // 调用 A 的 hello B.super.hello(); // 调用 B 的 hello System.out.println("Hello from C"); } }抽象类 vs 接口 对比对比维度抽象类 (abstract class)接口 (interface)关键字abstract classinterface继承/实现单继承（extends）多实现（implements）构造方法有构造方法没有构造方法实例变量可以有各种变量只有 public static final 常量方法可有抽象方法和具体方法JDK 8 前只有抽象方法；JDK 8+ 支持 default/static访问修饰符方法可以有任意访问级别方法默认 public成员访问可以有任何访问修饰符所有成员默认 public设计关系"is-a" 关系，表示本质"can-do" 关系，表示能力使用场景共享代码 + 模板方法模式定义契约（行为规范）如何选择使用抽象类的情况： - 需要在子类间共享代码（有方法实现） - 需要定义非 public 的成员 - 需要使用构造方法初始化状态 - 符合严格的 is-a 关系 使用接口的情况： - 定义不相关的类之间的共同行为（can-do） - 需要多重继承行为 - 只定义契约，不关心实现细节 - 希望支持未来的扩展（default 方法可向后兼容添加） 组合使用的情况： - 抽象类实现接口，提供默认实现 - 如：public abstract class AbstractList<E> implements List<E> { }// 组合使用示例 interface Payable { void pay(double amount); default void refund(double amount) { System.out.println("退款：" + amount); } } abstract class AbstractPayment implements Payable { protected String merchantId; public AbstractPayment(String merchantId) { this.merchantId = merchantId; } // 提供通用的验证逻辑 protected void validateAmount(double amount) { if (amount <= 0) throw new IllegalArgumentException("金额无效"); } // 留给子类实现 @Override public abstract void pay(double amount); } class WechatPay extends AbstractPayment { public WechatPay(String merchantId) { super(merchantId); } @Override public void pay(double amount) { validateAmount(amount); System.out.println("微信支付：" + amount); } }2.14 内部类概述内部类是定义在另一个类内部的类。Java 支持四种内部类：成员内部类、静态嵌套类、局部内部类、匿名类。1. 成员内部类（非静态内部类）定义在类中方法外的非静态类。public class Outer { private String outerField = "外部类字段"; private static String staticField = "静态字段"; // 成员内部类 public class Inner { // 不能定义静态成员（除 static final 常量外） // private static int x; // 编译错误！ public void display() { // 可以访问外部类的所有成员（包括 private） System.out.println("访问外部实例字段：" + outerField); System.out.println("访问外部静态字段：" + staticField); // 持有外部类引用 System.out.println("外部类引用：" + Outer.this); } } public void createInner() { Inner inner = new Inner(); // 在外部类内部创建 inner.display(); } } // 外部创建 public class Main { public static void main(String[] args) { // 创建成员内部类需要先有外部类对象 Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.display(); } }2. 静态嵌套类（Static Nested Class）使用 static 修饰的内部类。public class Outer { private String instanceField = "实例字段"; private static String staticField = "静态字段"; // 静态嵌套类 public static class StaticNested { private String nestedField = "嵌套类字段"; public void display() { // 只能访问外部类的静态成员 System.out.println("访问静态字段：" + staticField); // System.out.println(instanceField); // 编译错误！不能访问实例成员 // 不持有外部类引用，可以独立存在 } public static void staticMethod() { System.out.println("静态嵌套类的静态方法"); } } } // 使用 public class Main { public static void main(String[] args) { // 不需要外部类对象，直接创建 Outer.StaticNested nested = new Outer.StaticNested(); nested.display(); // 调用静态方法 Outer.StaticNested.staticMethod(); // 常见应用：Builder 模式 Computer pc = new Computer.Builder("i7", "16GB").gpu("RTX 4080").build(); } }3. 局部内部类定义在方法、构造方法或代码块中的类。public class Outer { public void processData(String prefix) { int localVar = 100; // 事实 final // 局部内部类 class DataProcessor { private String name; public DataProcessor(String name) { this.name = name; } public void process() { // 只能访问 final 或事实 final 的局部变量 System.out.println(prefix + name + " " + localVar); // localVar = 200; // 如果修改 localVar，上一行编译错误！ // 因为局部变量被内部类捕获后必须不可变 } } DataProcessor processor = new DataProcessor("处理器"); processor.process(); } }4. 匿名类没有名字的内部类，用于实现接口或继承类的一次性使用。public class AnonymousClassDemo { public static void main(String[] args) { // 1. 匿名类实现接口 Runnable task = new Runnable() { @Override public void run() { System.out.println("匿名类实现的线程任务"); } }; new Thread(task).start(); // 2. 匿名类继承抽象类 Animal dog = new Animal("旺财") { @Override public void makeSound() { System.out.println(name + " 汪汪叫！（来自匿名类）"); } }; dog.makeSound(); // 3. 匿名类作为参数传递 List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } }); // 4. JDK 8+ 可用 Lambda 替代匿名类 Collections.sort(names, (a, b) -> a.length() - b.length()); } }四种内部类对比类型修饰符访问外部成员持有外部引用可定义静态成员典型用途成员内部类无 static可访问所有是否（除 static final 常量）与外部类紧密关联的辅助类静态嵌套类static仅静态成员否是Builder 模式、独立辅助类局部内部类不能用访问修饰符可访问所有 + final 局部变量是否方法内临时使用的类匿名类无名可访问所有 + final 局部变量是否简单的一次性实现（监听器、Comparator）选择指南需要访问外部类实例成员 → 成员内部类 不需要外部类实例 → 静态嵌套类（优先选择，内存效率更高） 简单接口的一次性实现 → 匿名类（或 Lambda） 方法内部临时使用 → 局部内部类内存效率排序静态嵌套类 > 局部内部类 > 成员内部类 > 匿名类原因：静态嵌套类不持有外部引用，没有额外的内存开销；匿名类和成员内部类都隐式持有外部类引用，可能阻止外部类被 GC 回收。2.15 枚举(Enum)核心概念枚举（Enum）是一种特殊的类，用于定义一组固定的常量。所有枚举类都隐式继承 java.lang.Enum。基本定义与使用// 简单枚举 public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } // 使用 public class EnumBasicDemo { public static void main(String[] args) { Day today = Day.FRIDAY; // switch 中使用枚举 switch (today) { case MONDAY: System.out.println("周一综合征"); break; case FRIDAY: System.out.println("TGIF!"); break; case SATURDAY: case SUNDAY: System.out.println("周末愉快！"); break; default: System.out.println("工作日"); } } }带属性和方法的枚举public enum Season { // 枚举实例（调用构造方法） SPRING("春天", "温暖", 1), SUMMER("夏天", "炎热", 2), AUTUMN("秋天", "凉爽", 3), WINTER("冬天", "寒冷", 4); // 成员变量 private final String chineseName; private final String description; private final int order; // 构造方法必须是 private（默认就是 private） Season(String chineseName, String description, int order) { this.chineseName = chineseName; this.description = description; this.order = order; } // getter 方法 public String getChineseName() { return chineseName; } public String getDescription() { return description; } public int getOrder() { return order; } // 自定义方法 public boolean isHotSeason() { return this == SUMMER; } public boolean isColdSeason() { return this == WINTER; } } // 使用 public class Main { public static void main(String[] args) { Season s = Season.SUMMER; System.out.println(s.getChineseName()); // 夏天 System.out.println(s.getDescription()); // 炎热 System.out.println(s.isHotSeason()); // true } }枚举的常用方法public class EnumMethodsDemo { public static void main(String[] args) { Day day = Day.FRIDAY; // name()：返回枚举常量名（字符串） System.out.println(day.name()); // FRIDAY // ordinal()：返回枚举常量的序数（从 0 开始） System.out.println(day.ordinal()); // 4（第 5 个枚举值） // toString()：默认返回 name()，可重写 System.out.println(day.toString()); // FRIDAY // valueOf(String)：通过名称获取枚举常量 Day d = Day.valueOf("MONDAY"); System.out.println(d); // MONDAY // Day d2 = Day.valueOf("INVALID"); // 抛出 IllegalArgumentException // values()：返回所有枚举常量的数组 Day[] allDays = Day.values(); for (Day each : allDays) { System.out.print(each + " "); } // 输出：MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY // compareTo()：比较顺序（基于 ordinal） System.out.println(Day.MONDAY.compareTo(Day.FRIDAY)); // 负数（MONDAY 在 FRIDAY 之前） System.out.println(Day.FRIDAY.compareTo(Day.MONDAY)); // 正数 System.out.println(Day.MONDAY.compareTo(Day.MONDAY)); // 0 } }枚举比较public class EnumComparison { public static void main(String[] args) { Day d1 = Day.MONDAY; Day d2 = Day.MONDAY; Day d3 = Day.FRIDAY; // == 和 equals() 都可以用于枚举比较（推荐使用 ==，因为枚举是单例） System.out.println(d1 == d2); // true System.out.println(d1.equals(d2)); // true System.out.println(d1 == d3); // false // 枚举实现了 Comparable 接口 // 不能用 > 和 < 直接比较！ // if (d1 < d3) { } // 编译错误！ // 使用 compareTo() 进行比较 if (d1.compareTo(d3) < 0) { System.out.println(d1 + " 在 " + d3 + " 之前"); } } }枚举实现接口// 定义接口 interface Operator { double apply(double a, double b); } // 枚举实现接口 public enum MathOperator implements Operator { ADD { @Override public double apply(double a, double b) { return a + b; } }, SUBTRACT { @Override public double apply(double a, double b) { return a - b; } }, MULTIPLY { @Override public double apply(double a, double b) { return a * b; } }, DIVIDE { @Override public double apply(double a, double b) { if (b == 0) throw new ArithmeticException("除数不能为0"); return a / b; } }; } // 使用 public class Main { public static void main(String[] args) { double result = MathOperator.ADD.apply(10, 5); System.out.println("10 + 5 = " + result); // 15.0 double result2 = MathOperator.MULTIPLY.apply(3, 4); System.out.println("3 * 4 = " + result2); // 12.0 } }枚举的注意事项要点说明构造方法必须是 private枚举构造方法默认就是 private，且不能改为 public 或 protected不能用 > 和 < 直接比较枚举不支持 < > 运算符，使用 compareTo() 方法== 和 equals() 都可用枚举是单例模式，推荐使用 == 进行引用比较（更高效，且不会 NPE）ordinal() 不建议在业务逻辑中使用ordinal() 依赖枚举声明顺序，调整顺序会导致逻辑错误。应使用自定义属性替代可以实现接口枚举可以实现一个或多个接口不能继承类枚举已经隐式继承 java.lang.Enum，Java 单继承限制导致不能再继承其他类// 正确实践：用自定义属性替代 ordinal() public enum ErrorCode { SUCCESS(200, "成功"), BAD_REQUEST(400, "错误的请求"), UNAUTHORIZED(401, "未授权"), NOT_FOUND(404, "未找到"), INTERNAL_ERROR(500, "服务器内部错误"); private final int code; private final String message; ErrorCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } // 通过 code 查找对应的枚举值 public static ErrorCode fromCode(int code) { for (ErrorCode ec : values()) { if (ec.code == code) { return ec; } } throw new IllegalArgumentException("未知的错误码：" + code); } }2.16 递归核心概念递归（Recursion）是指一个方法在其方法体内调用自身来解决问题的编程技巧。递归通常将一个复杂问题分解为规模更小的同类子问题。递归的三要素1. 递归终止条件（Base Case）：什么情况下停止递归 2. 递归调用（Recursive Call）：如何将问题分解为更小的子问题 3. 递归前进（Progress）：每次调用都要向终止条件靠近线性递归每次递归只产生一个子问题，递归深度与问题规模成正比 O(n)。public class LinearRecursion { /** * 计算阶乘 n! * 递归公式：n! = n × (n−1)!，且 0! = 1 */ public static long factorial(int n) { // 终止条件（Base Case） if (n == 0 || n == 1) { return 1; } // 递归调用：n * (n-1)! return n * factorial(n - 1); } /** * 计算斐波那契数列第 n 项（线性递归优化版） * F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2) */ public static long fibonacci(int n) { return fibonacciHelper(n, 0, 1); } // 尾递归辅助方法（Java 不优化尾递归，但逻辑清晰） private static long fibonacciHelper(int n, long a, long b) { if (n == 0) return a; if (n == 1) return b; return fibonacciHelper(n - 1, b, a + b); } /** * 计算数组元素之和 */ public static int arraySum(int[] arr, int index) { if (index >= arr.length) { return 0; // 终止条件 } return arr[index] + arraySum(arr, index + 1); } /** * 反转字符串 */ public static String reverse(String str) { if (str.isEmpty()) { return str; // 终止条件 } return reverse(str.substring(1)) + str.charAt(0); } public static void main(String[] args) { System.out.println("5! = " + factorial(5)); // 120 System.out.println("F(10) = " + fibonacci(10)); // 55 System.out.println("数组和 = " + arraySum(new int[]{1,2,3,4,5}, 0)); // 15 System.out.println("反转 = " + reverse("Hello")); // olleH } }二分递归每次递归产生两个子问题，可能导致大量重复计算。时间复杂度通常为 O(2^n)。public class BinaryRecursion { /** * 斐波那契数列（朴素二分递归） * 问题：存在大量重复计算。F(5) 需要计算 F(3) 两次，F(2) 三次... */ public static long fibonacciNaive(int n) { // 终止条件 if (n <= 1) { return n; } // 二分递归：每次产生两个子问题 return fibonacciNaive(n - 1) + fibonacciNaive(n - 2); } /** * 二分查找（递归实现） */ public static int binarySearch(int[] arr, int target, int left, int right) { if (left > right) { return -1; // 未找到 } int mid = left + (right - left) / 2; if (arr[mid] == target) { return mid; } else if (arr[mid] > target) { return binarySearch(arr, target, left, mid - 1); } else { return binarySearch(arr, target, mid + 1, right); } } /** * 归并排序（递归实现） */ public static void mergeSort(int[] arr, int left, int right) { if (left >= right) { return; // 终止条件：单个元素或空区间 } int mid = left + (right - left) / 2; // 二分递归 mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); // 合并 merge(arr, left, mid, right); } private static void merge(int[] arr, int left, int mid, int right) { int[] temp = new int[right - left + 1]; int i = left, j = mid + 1, k = 0; while (i <= mid && j <= right) { temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++]; } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } System.arraycopy(temp, 0, arr, left, temp.length); } public static void main(String[] args) { // 斐波那契（注意：n=50 时朴素版本会很慢） System.out.println("F(10) 朴素 = " + fibonacciNaive(10)); // 55 // 二分查找 int[] arr = {1, 3, 5, 7, 9, 11, 13}; int index = binarySearch(arr, 7, 0, arr.length - 1); System.out.println("7 的索引 = " + index); // 3 // 归并排序 int[] data = {38, 27, 43, 3, 9, 82, 10}; mergeSort(data, 0, data.length - 1); System.out.println("排序结果：" + Arrays.toString(data)); // [3, 9, 10, 27, 38, 43, 82] } }线性递归 vs 二分递归对比对比维度线性递归二分递归子问题数量每次 1 个每次 2 个时间复杂度通常 O(n)通常 O(2^n)（朴素）、O(n log n)（归并/快排）重复计算一般不重复可能大量重复（如朴素斐波那契）递归深度与输入规模成正比通常 log n 级别（分治）典型应用阶乘、链表遍历、字符串处理斐波那契、归并排序、快速排序、二叉树遍历优化策略可转为尾递归/迭代可使用记忆化搜索（Memoization）递归优化：记忆化搜索public class MemoizationDemo { // 记忆化搜索：缓存已计算的结果，避免重复计算 private static Map<Integer, Long> memo = new HashMap<>(); public static long fibonacciMemo(int n) { if (n <= 1) { return n; } // 如果已经计算过，直接返回缓存结果 if (memo.containsKey(n)) { return memo.get(n); } // 计算并缓存 long result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2); memo.put(n, result); return result; } // 数组版本（更高效） public static long fibonacciMemoArray(int n) { long[] memo = new long[n + 1]; return fib(n, memo); } private static long fib(int n, long[] memo) { if (n <= 1) return n; if (memo[n] != 0) return memo[n]; memo[n] = fib(n - 1, memo) + fib(n - 2, memo); return memo[n]; } public static void main(String[] args) { System.out.println("F(50) 记忆化 = " + fibonacciMemo(50)); // 快速计算结果，而不是像朴素版本那样指数级耗时 } }递归的注意事项注意点说明必须有终止条件否则会导致无限递归，最终触发 StackOverflowError向终止条件靠近每次递归调用都应在问题规模上向 Base Case 靠近注意栈溢出Java 栈深度有限（默认约 1MB），深度递归可能 StackOverflowError警惕重复计算二分递归可能指数级重复，使用记忆化或动态规划优化递归转迭代任何递归都可以转换为迭代实现，迭代更省内存避免过深递归对于超大数据量，优先考虑迭代方案// 递归转迭代示例：阶乘 public static long factorialIterative(int n) { long result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result; } // 递归转迭代示例：斐波那契（动态规划） public static long fibonacciDP(int n) { if (n <= 1) return n; long prev2 = 0, prev1 = 1, current = 0; for (int i = 2; i <= n; i++) { current = prev2 + prev1; prev2 = prev1; prev1 = current; } return current; }全文完。本笔记涵盖了 Java 面向对象编程的核心知识点，包括类与对象、方法、构造方法、访问修饰符、this/super/static/final 关键字、继承、封装、多态、抽象类与接口、内部类、枚举和递归等 16 个主题，适合作为 Java OOP 学习与复习的参考资料。
1.1 基本数据类型Java 是一门强类型语言，共有 8 种基本数据类型（Primitive Type）分为三大类：数值型（整型，浮点型）、字符型和布尔型类型总览分类类型字节数取值范围默认值备注整型byte1-128 ~ 1270 整型short2-32768 ~ 327670 整型int4-2^31 ~ 2^31-10默认整数类型整型long8-2^63 ~ 2^63-10L字面量需加 L 后缀浮点型float4±3.4E-38 ~ ±3.4E+380.0f字面量需加 f 后缀浮点型double8±1.7E-308 ~ ±1.7E+3080.0d默认浮点类型字符型char20 ~ 65535 (Unicode)'\u0000'表示单个 Unicode 字符布尔型boolean1位true / falsefalseJVM 中通常用 int 表示代码示例public class PrimitiveTypes { public static void main(String[] args) { // 整型 byte b = 100; // 1字节 short s = 10000; // 2字节 int i = 100000; // 4字节，默认 long l = 10000000000L; // 8字节，必须加 L 后缀 // 浮点型 float f = 3.14f; // 4字节，必须加 f 后缀 double d = 3.1415926535; // 8字节，默认 // 字符型 char c1 = 'A'; // 2字节 char c2 = '\u0041'; // Unicode 编码表示 'A' char c3 = 65; // ASCII 码表示 'A' // 布尔型 boolean flag = true; } }注意事项long 和 float 的字面量后缀不可省略：不加 L 的整数字面量默认是 int，不加 f 的浮点字面量默认是 double。char 可以存中文：因为 char 是 2 字节 Unicode，一个中文字符正好是 2 字节。boolean 占用空间：JVM 规范中 boolean 被当作 int 处理（4字节），但在 boolean 数组中每个元素占用 1 字节。默认值仅对成员变量有效：局部变量必须显式初始化，否则编译不通过。public class DefaultValues { // 实例变量：自动初始化为默认值 byte b; // 0 short s; // 0 int i; // 0 long l; // 0L float f; // 0.0f double d; // 0.0d char c; // '\u0000' boolean flag; // false public void test() { int local; // 局部变量 // System.out.println(local); // 编译错误！未初始化 } }1.2 变量类型Java 中有三种变量，按声明位置和修饰符区分。对比总览变量类型声明位置生命周期默认值存储位置修饰符局部变量方法、构造方法或代码块中方法执行期间无，必须显式初始化栈内存（方法栈帧）只能用 final实例变量类中、方法外与对象共存亡有默认值堆内存（对象中）访问控制修饰符类变量（静态变量）类中、方法外，static 修饰类加载时创建，类卸载时销毁有默认值方法区/元空间static + 访问控制修饰符内存模型 JDK8+┌─────────────────────────────────────────────┐ │ JVM 内存结构 │ ├───────────────┬───────────────┬─────────────┤ │ 虚拟机栈 │ 堆内存 │ 方法区 │ │ (Stack) │ (Heap) │ Metaspace(元空间) │ ├───────────────┼───────────────┼─────────────┤ │ 方法栈帧 │ 对象实例 │ 类元信息(类结构、字段、方法字节码) │ │ 局部变量表 │ 数组 │ 运行时常量池、符号表、Vtable虚方法表 │ │ 操作数栈 │ 实例变量 │ 即时编译代码、类加载信息 │ │ 方法返回地址 │ Class对象、静态变量、字符串常量池 │ │ 动态链接 │ │ │ 线程私有 │ 线程共享 │ 线程共享 │ └───────────────┴───────────────┴─────────────┘ 运行时常量池 → 永远在元空间（方法区） 方法栈帧：每调用一个方法，就会在虚拟机栈创建一块独立内存 = 栈帧 如调用 sayHello() → 生成 sayHello() 栈帧 调用 main() → 生成 main() 栈帧 方法执行完，栈帧直接弹出销毁。 局部变量表存放：局部变量、方法参数、对象引用 操作数栈：JVM 运算专用临时空间，做加减、赋值、打印时中转数据 方法返回地址： 方法执行完后，要回到调用处继续执行，记录跳转位置。 动态链接:栈帧保存符号引用，运行时把符号引用解析成真实内存地址 比如代码里 System.out.println，编译时只是符号，运行时动态找到对应方法地址。 对象实例:new User("张三") 在堆中创建一块内存，就是 User 对象实例 数组:所有数组无论基本类型还是引用类型，本体都在堆。 实例变量:属于单个对象，存在对应对象实例内部 Class 对象、静态变量,每个类在堆里唯一存在一个 Class 对象 User.class静态变量 school 不归属任何实例，存在堆的 User.class 对象中： 字符串常量池 "北大" "张三" "李四" 这类双引号字面量，存放在堆的 StringTable 字符串常量池 元空间（方法区）只存类模板，不存对象数据 类元信息 (类结构、字段、方法字节码)编译后 User.class 文件里的固定模板，加载后放入元空间 运行时常量池、符号表:运行时常量池：class 文件自带常量池加载进来，存数字、符号引用 符号表：记录类名、方法名、字段名等符号，供动态链接使用 Vtable 虚方法表:存放当前类所有重写 / 继承的虚方法地址，多态调用时快速查找方法。 比如如果 User 有父类，重写 toString ()，虚表会记录 toString 的内存地址 即时编译代码（JIT） 热点方法被多次调用，JIT 把字节码编译成本地机器码，缓存到元空间，提升执行速度 类加载信息 记录当前类由哪个类加载器加载、父类、接口、访问权限等元数据。 栈：方法执行现场，无对象本体 堆：所有对象、数组、静态变量、字符串池 元空间：类的模板定义（只存描述，不存数据值） 通俗总览 栈管运行（方法调用），堆管存储（对象、静态数据），元空间管类模板（类定义信息） 栈内存 (Stack) —— 线程的 “执行剧本” 核心职责：存储每个方法调用对应的栈帧，描述方法执行过程 存储内容：局部变量表（基本类型、对象引用）、操作数栈、方法返回地址、动态链接 生命周期：方法调用创建栈帧，方法执行完毕立刻销毁，随线程生随灭 内存分配：连续内存，栈大小固定（可通过 -Xss 设置） 线程模型：线程私有，天然线程安全，不存在并发竞争 补充：不存储方法代码，方法字节码存在元空间 堆内存 (Heap) —— GC 的 “主战场” 核心职责：存储运行时动态创建的数据，所有 new 出来的对象、数组 存储内容：实例对象、数组、实例变量、Class 对象、静态变量、字符串常量池 生命周期：对象无有效引用后等待 GC 回收，存活时间不确定 内存分配：非连续内存，动态扩展；-Xms 初始堆，-Xmx 最大堆，是 JVM 最大内存区域 线程模型：线程共享，存在线程安全问题；通过 TLAB 线程本地分配缓冲区优化并发分配 方法区 / 元空间 (Method Area / Metaspace) —— 类的 “档案馆” 核心职责：存储类元信息（类结构、字段、方法字节码）、运行时常量池、符号引用 版本重大变化（Java 8）： 彻底移除永久代（PermGen），改用元空间 Metaspace 元空间使用本地内存（Native Memory），不再占用 JVM 堆内存 JDK7 起就将静态变量、字符串常量池移出永久代，放入堆内存 生命周期：类加载时创建类元数据，类完全卸载（ClassLoader 被回收）才释放 线程模型：线程共享，类加载、反射访问需要保证线程安全 典型异常：OutOfMemoryError: Metaspace，加载类过多耗尽本地内存 GC 特点：回收频率极低，仅 Full GC 时尝试卸载无用类、回收废弃常量 混淆 局部变量 → 虚拟机栈 实例变量 → 堆（对应实例对象内部） 静态变量 → JDK7+ 堆（Class 对象末尾） 方法代码、类结构 → 元空间 运行时常量池 → 元空间 字符串常量池 (StringTable) → JDK7+ 堆 Class反射对象 → 堆；描述这个类的元数据 → 元空间 快速记忆： 栈：线程私有，存局部变量、方法执行现场，用完即销毁，无 GC 堆：GC 主战场，所有对象、数组、静态变量、字符串池，线程共享 元空间：方法区新实现，本地内存，只存类的模板信息，不存对象数据代码示例public class VariableTypes { // 类变量（静态变量） static int staticVar = 100; static final String CONSTANT = "常量"; // 实例变量 int instanceVar = 200; String name; public void method(int param) { // param 也是局部变量 // 局部变量 int localVar = 300; String localStr; // System.out.println(localStr); // 编译错误！未初始化 System.out.println(localVar); // 300 System.out.println(instanceVar); // 200 System.out.println(staticVar); // 100 { // 代码块中的局部变量 int blockVar = 400; System.out.println(blockVar); } // System.out.println(blockVar); // 编译错误！超出作用域 } }注意事项局部变量必须显式初始化，否则编译报错。实例变量的默认值可以依赖，但不推荐 —— 显式初始化是良好习惯。静态变量在类加载时初始化，只会初始化一次，所有实例共享。JDK 8 之后，方法区被元空间（Metaspace）取代，使用本地内存，不再受 JVM 堆大小限制。1.3 运算符运算符分类算术运算符运算符含义示例+加 / 字符串拼接a + b-减a - b*乘a * b/除a / b%取模（取余）a % b++自增a++ 或 ++a--自减a-- 或 --a关系运算符（比较运算符）运算符含义结果类型==等于boolean!=不等于boolean>大于boolean<小于boolean>=大于等于boolean<=小于等于boolean逻辑运算符运算符含义短路特性&&逻辑与是（左边为 false 时不执行右边）` `逻辑或是（左边为 true 时不执行右边）!逻辑非—&按位与 / 非短路逻辑与否``按位或 / 非短路逻辑或否位运算符运算符含义&按位与``按位或^按位异或~按位取反<<左移>>右移（带符号）>>>无符号右移赋值运算符=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=运算符优先级优先级运算符结合性高() [] .左 ! ~ ++ --右 * / %左 + -左 << >> >>>左 < <= > >= instanceof左 == !=左 &左 ^左 ``左 &&左 ` `左 ?:（三元）右低= += 等赋值运算符右记忆口诀：乘除 > 加减 > 比较 > 逻辑 > 赋值字符串比较：== vs equals()String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello"); System.out.println(s1 == s2); // true (同一字符串池对象) System.out.println(s1 == s3); // false (不同对象) System.out.println(s1.equals(s3)); // true (内容相同)最佳实践// 1. 使用括号明确优先级 int result = (a + b) * c / (d - e); // 清晰 // 2. 浮点数比较使用误差范围 double x = 0.1 + 0.2; double y = 0.3; if (Math.abs(x - y) < 1e-10) { // 正确做法 System.out.println("相等"); } // if (x == y) { ... } // 错误！可能不相等 // 3. 避免在复杂表达式中使用自增自减 int i = 5; int j = i++ + ++i; // 难以理解，避免使用 // 建议分开写： i++; int k = i; i++; j = k + i; // 4. 短路求值的妙用 if (obj != null && obj.getValue() > 0) { // 安全：先判空 // ... }1.4 类型转换自动类型转换（隐式转换）规则：小范围类型自动转换为大范围类型，不会丢失精度。byte → short → int → long → float → double char ↗byte b = 10; short s = b; // byte → short，自动 int i = s; // short → int，自动 long l = i; // int → long，自动 float f = l; // long → float，自动（但可能损失精度！） double d = f; // float → double，自动 char c = 'A'; int ci = c; // char → int，自动（ci = 65）注意：long（8字节）转 float（4字节）虽然范围更大，但可能损失精度，因为 float 的有效位数（约7位）不如 long（19位）。强制类型转换（显式转换）规则：大范围类型转为小范围类型，需要使用强制转换符，可能丢失数据。double d = 3.99; int i = (int) d; // i = 3，小数部分被截断（不是四舍五入） long l = 300L; byte b = (byte) l; // b = 44，高位被截断，结果不可预测 // 溢出示例 int big = 130; byte small = (byte) big; // small = -126（byte 范围 -128~127，溢出循环）包装类与自动装箱/拆箱// 自动装箱：基本类型 → 包装类 Integer a = 10; // 等价于 Integer.valueOf(10) Double d = 3.14; // 等价于 Double.valueOf(3.14) // 自动拆箱：包装类 → 基本类型 int b = a; // 等价于 a.intValue() double e = d; // 等价于 d.doubleValue() // 混合运算时自动拆箱 Integer x = 100; Integer y = 200; int sum = x + y; // 自动拆箱后计算Integer 缓存机制Integer i1 = 127; Integer i2 = 127; System.out.println(i1 == i2); // true（缓存范围内，同一对象） Integer i3 = 128; Integer i4 = 128; System.out.println(i3 == i4); // false（超出缓存范围，不同对象） // 正确的比较方式 System.out.println(i3.equals(i4)); // trueInteger 默认缓存 -128 到 127 之间的值（上限可通过 -XX:AutoBoxCacheMax 调整）。字符串与基本类型的转换// 字符串 → 基本类型 int i = Integer.parseInt("123"); double d = Double.parseDouble("3.14"); boolean b = Boolean.parseBoolean("true"); // 基本类型 → 字符串 String s1 = String.valueOf(123); String s2 = Integer.toString(456); String s3 = 789 + ""; // 简单但不推荐 String s4 = String.format("%d", 100);1.5 数组数组特点类型统一：所有元素必须是同一类型长度固定：创建后不可改变长度索引访问：从 0 开始，最大为 length - 1连续存储：内存中连续分配，支持高效随机访问声明与创建// 声明（不能指定长度！） int[] arr1; // 推荐写法 类型[] 变量名 int arr2[]; // C 风格，不推荐 // 创建（必须指定长度） arr1 = new int[5]; // 所有元素初始化为 0 String[] strs = new String[3]; // 所有元素初始化为 null // 声明+创建一步完成 double[] nums = new double[10];静态初始化// 方式一：简写形式 int[] arr1 = {1, 2, 3, 4, 5}; // 方式二：完整形式 int[] arr2 = new int[]{1, 2, 3, 4, 5}; // 注意：简写形式只能用于声明时。不能单独用在赋值语句里。 // int[] arr3; // arr3 = {1, 2, 3}; // 编译错误！ // int[] arr3 = {1,2,3} //不然就老老实实 new int[] int[] arr3; arr3 = new int[]{1, 2, 3}; // 正确多维数组// 规则二维数组（3行4列） int[][] matrix = new int[3][4]; matrix[0][0] = 1; matrix[2][3] = 12; // 静态初始化二维数组 int[][] grid = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // 不规则数组（每行长度不同） int[][] jagged = new int[3][]; jagged[0] = new int[2]; jagged[1] = new int[4]; jagged[2] = new int[3];数组操作遍历int[] arr = {1, 2, 3, 4, 5}; // 方式一：for 循环（有索引） for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } // 方式二：for-each（简洁，无索引） for (int value : arr) { System.out.println(value); // 注意：修改 value 不会影响原数组！ }复制(重点)//这三种方法对数组本身执行的都是浅拷贝 int[] src = {1, 2, 3, 4, 5}; // 方式一：手动循环 int[] dest1 = new int[src.length]; for (int i = 0; i < src.length; i++) { dest1[i] = src[i]; } // 方式二：System.arraycopy()（最高效） (速度最快，native方法，直接操作内存) int[] dest2 = new int[src.length]; System.arraycopy(src, 0, dest2, 0, src.length); // 方式三：Arrays.copyOf()（最简洁） 利用 System.arraycopy()，可指定新数组长度。 int[] dest3 = Arrays.copyOf(src, src.length); // 方式四：数组.clone() int[] dest4 = src.clone();排序int[] arr = {5, 2, 8, 1, 9}; // Arrays.sort()（双轴快速排序，O(n log n)） Arrays.sort(arr); System.out.println(Arrays.toString(arr)); // [1, 2, 5, 8, 9] // 部分排序 Arrays.sort(arr, 1, 4); // 只排序索引 [1, 4) 的元素 // 冒泡排序（手写版，O(n²)） void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } // 选择排序（手写版，O(n²)） void selectionSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { int minIdx = i; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[minIdx]) { minIdx = j; } } int temp = arr[i]; arr[i] = arr[minIdx]; arr[minIdx] = temp; } }搜索int[] arr = {1, 3, 5, 7, 9}; // 线性搜索 O(n) int linearSearch(int[] arr, int target) { for (int i = 0; i < arr.length; i++) { if (arr[i] == target) return i; } return -1; } // 二分搜索 O(log n)（前提：数组必须有序） int index = Arrays.binarySearch(arr, 5); // 返回 2 int notFound = Arrays.binarySearch(arr, 4); // 返回 -3（-(插入点) - 1）注意事项arr.length 获取长度，是属性不是方法（区别于 String.length()）。for-each 中修改临时变量不影响原数组，需要修改元素请使用普通 for 循环。遍历时不能修改数组结构（增删元素），但普通 for 循环中修改已有元素是可以的。数组越界：访问 arr[arr.length] 会抛出 ArrayIndexOutOfBoundsException。1.6 String 字符串核心特点不可变（Immutable）：String 是 final 类，一旦创建内容不可改变。线程安全：不可变性天然保证线程安全。字符串池（String Pool）：JVM 维护一个字符串常量池，相同字面量复用同一对象。创建方式// 方式一：字面量（进入字符串池） String s1 = "Hello"; // 方式二：new（不进入字符串池，在堆中创建新对象） String s2 = new String("Hello"); // 方式三：字符数组 char[] chars = {'H', 'e', 'l', 'l', 'o'}; String s3 = new String(chars); // 验证 System.out.println(s1 == s2); // false System.out.println(s1 == s3); // false System.out.println(s1.equals(s2)); // true常用方法String str = " Hello World "; // 长度和判空 str.length(); // 13（包含两边空格） str.isEmpty(); // false str.isBlank(); // false（JDK 11+，只判断是否空白字符） // 查找 str.indexOf('o'); // 5（首次出现位置） str.indexOf("World"); // 7 str.lastIndexOf('o'); // 8 // 比较 str.equals("Hello"); // false str.equalsIgnoreCase(" hello world "); // true str.compareTo("ABC"); // 字典序比较 // 判断前缀后缀 str.startsWith(" He"); // true str.endsWith("d "); // true str.contains("Wo"); // true // 截取 str.substring(1, 6); // "Hello"（[1, 6) 左闭右开） // 替换 str.replace('l', 'L'); // " HeLLo WorLd " str.replace("Hello", "Hi"); // " Hi World " str.replaceAll("\\s", "_"); // "_Hello_World_"（正则替换） // 去空格 str.trim(); // "Hello World"（去掉首尾半角空格） str.strip(); // "Hello World"（JDK 11+，支持全角空格） // 大小写 str.toUpperCase(); // " HELLO WORLD " str.toLowerCase(); // " hello world " // 分割与拼接 String[] parts = "a,b,c".split(","); // ["a", "b", "c"] String joined = String.join("-", "A", "B", "C"); // "A-B-C"intern() 方法String s1 = new String("hello"); String s2 = "hello"; System.out.println(s1 == s2); // false String s3 = s1.intern(); // 将 s1 的内容加入字符串池 System.out.println(s3 == s2); // trueStringBuilder vs StringBuffer vs String特性StringStringBuilderStringBuffer可变性不可变可变可变线程安全安全（不可变）不安全安全（synchronized）性能拼接时最慢（频繁创建新对象）最快较快适用场景少量字符串操作单线程频繁修改多线程频繁修改// StringBuilder 示例（推荐单线程下使用） StringBuilder sb = new StringBuilder(); sb.append("Hello"); sb.append(" "); sb.append("World"); sb.insert(5, ","); // "Hello, World" sb.replace(0, 5, "Hi"); // "Hi, World" sb.delete(4, 5); // "Hi, World" sb.reverse(); // "dlroW ,iH" String result = sb.toString(); // StringBuffer 示例（多线程安全） StringBuffer sbf = new StringBuffer(); sbf.append("Thread-safe");频繁拼接的性能对比// 低效方式：每次 + 都创建新的 String 对象 String s = ""; for (int i = 0; i < 10000; i++) { s += i; // 创建 10000 个临时对象 } // 高效方式 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.append(i); // 只在内部数组扩容 } String result = sb.toString();1.7 输入输出（Scanner）基本使用import java.util.Scanner; public class ScannerDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读取字符串 System.out.print("请输入姓名："); String name = scanner.next(); // 读到空格/换行停止 System.out.print("请输入年龄："); int age = scanner.nextInt(); // 读取整数 System.out.print("请输入身高："); double height = scanner.nextDouble(); // 读取浮点数 System.out.print("是否成年："); boolean adult = scanner.nextBoolean(); // 读取布尔值 System.out.println("姓名：" + name + "，年龄：" + age); } }常用方法方法说明next()读取下一个单词（空格/换行分隔）nextLine()读取整行（到换行为止）nextInt()读取 intnextDouble()读取 doublenextBoolean()读取 booleanhasNext()是否有下一个标记hasNextInt()下一个标记是否能转为 intclose()关闭 Scanner缓冲区问题（重点）Scanner scanner = new Scanner(System.in); System.out.print("请输入年龄："); int age = scanner.nextInt(); // 读取 25，但换行符 \n 留在缓冲区 // System.out.print("请输入姓名："); // String name = scanner.nextLine(); // 直接读取了残留的 \n，返回空字符串！ // 解决方案：消耗掉残留的换行符 scanner.nextLine(); // 清除缓冲区的 \n System.out.print("请输入姓名："); String name = scanner.nextLine(); // 正常读取 System.out.println("年龄：" + age + "，姓名：" + name);输入校验import java.util.Scanner; import java.util.InputMismatchException; Scanner scanner = new Scanner(System.in); int number = 0; boolean valid = false; while (!valid) { try { System.out.print("请输入一个整数："); number = scanner.nextInt(); valid = true; } catch (InputMismatchException e) { System.out.println("输入无效，请重新输入！"); scanner.next(); // 清除无效输入 } } System.out.println("你输入的是：" + number);最佳实践// 1. 使用完毕后关闭（System.in 一般不关，但在文件/流场景必须关） Scanner scanner = new Scanner(System.in); // ... 操作 ... scanner.close(); // 2. 格式化输出 String name = "张三"; int score = 95; System.out.printf("姓名：%s，成绩：%d%n", name, score); System.out.format("姓名：%-10s 成绩：%03d%n", name, score); // 3. 提供清晰输入提示 System.out.println("=== 学生信息录入 ==="); System.out.print("姓名（字符串）：");格式化输出常用占位符占位符说明示例%s字符串printf("%s", "Hello")%d十进制整数printf("%d", 100)%f浮点数printf("%.2f", 3.1415) → 3.14%c字符printf("%c", 'A')%b布尔值printf("%b", true)%n换行符（跨平台） %x十六进制printf("%x", 255) → ff1.8 包管理包（Package）概述包用于组织 Java 类，防止命名冲突，提供访问保护。Java 使用包来模拟文件系统的目录结构。package 语句// 必须是 Java 文件的第一行非注释代码 package com.example.util; // 一个 Java 文件只能有一个 package 语句 public class StringUtils { public static boolean isEmpty(String str) { return str == null || str.length() == 0; } }import 语句// 位于 package 之后，类定义之前 package com.example.app; import java.util.List; // 导入单个类 import java.util.ArrayList; // 导入单个类 import java.util.*; // 导入整个包（不导入子包） import static java.lang.Math.*; // 静态导入 public class Demo { public static void main(String[] args) { List<String> list = new ArrayList<>(); // 静态导入后可直接使用方法 double result = sqrt(pow(2, 3) + abs(-4)); // 不需要 Math. 前缀 double pi = PI; } }静态导入详解// 导入特定静态方法 import static java.lang.Math.max; import static java.lang.Math.min; // 导入所有静态成员 import static java.lang.Math.*; int bigger = max(10, 20); // 无需 Math.max() int smaller = min(10, 20); // 无需 Math.min() double pi = PI; // 无需 Math.PI注意：过多使用静态导入会降低可读性，建议仅在频繁使用（如数学计算、测试断言）时使用。目录结构对应关系包名和目录结构必须严格对应：项目根目录/ └── src/ └── com/ └── example/ ├── util/ │ ├── StringUtils.java → package com.example.util; │ └── DateUtils.java → package com.example.util; ├── model/ │ └── User.java → package com.example.model; └── app/ └── Main.java → package com.example.app;包命名规范规范示例全小写字母com.example.myapp域名倒序com.google.gson避免关键字不要用 java、javax 开头层级用点分隔com.company.project.moduleJava 常用包一览（重点）包名主要功能java.lang核心类（自动导入）：String、Math、System、Thread 等java.util工具类：集合框架、日期、Scanner、Random 等java.io输入输出：File、InputStream、Reader 等java.net网络编程：Socket、URL、HttpURLConnection 等java.sql数据库操作：Connection、ResultSet、Statement 等java.math高精度数学：BigInteger、BigDecimal 等附录：速查表基本类型默认值与范围类型默认值最小值最大值包装类byte0-128127Byteshort0-3276832767Shortint0-2^312^31-1Integerlong0L-2^632^63-1Longfloat0.0f——Floatdouble0.0d——Doublechar'\u0000'065535Characterbooleanfalse——Boolean数组 vs 集合特性数组集合（ArrayList等）长度固定动态可变类型可存基本类型和对象只能存对象（自动装箱解决）性能更高有扩容开销API较少丰富（增删改查排序等）本文档涵盖了 Java 基础语法的核心知识点：基本数据类型、变量与内存模型、运算符、类型转换、数组操作、字符串处理、Scanner 输入输出以及包管理机制。
🌐 Spring MVC 知识地图一、核心架构与组件Spring MVC 核心 ├── DispatcherServlet（前端控制器） ├── HandlerMapping（处理器映射） │ ├── RequestMappingHandlerMapping （注解映射，最常用） │ ├── BeanNameUrlHandlerMapping │ └── SimpleUrlHandlerMapping ├── HandlerAdapter（处理器适配器） │ ├── RequestMappingHandlerAdapter │ ├── HttpRequestHandlerAdapter │ └── SimpleControllerHandlerAdapter ├── Handler / Controller（处理器） ├── ModelAndView（模型与视图封装） ├── ViewResolver（视图解析器） │ ├── InternalResourceViewResolver（JSP） │ ├── BeanNameViewResolver │ ├── XmlViewResolver │ ├── ContentNegotiatingViewResolver │ └── …… └── View（视图） ├── JSP / JSTL ├── Thymeleaf ├── FreeMarker └── ……二、请求处理完整流程客户端请求 → DispatcherServlet 接收 ├── 1. 通过 HandlerMapping 查找 Handler（返回 HandlerExecutionChain，包含拦截器） ├── 2. 通过 HandlerAdapter 执行 Handler（Controller） ├── 3. Handler 执行业务逻辑，返回 ModelAndView ├── 4. 通过 ViewResolver 解析 ModelAndView 得到 View 对象 └── 5. View 渲染，将模型数据填充到视图，生成响应三、控制器开发Controller 开发 ├── 注解 │ ├── @Controller / @RestController │ ├── @RequestMapping │ │ ├── @GetMapping │ │ ├── @PostMapping │ │ ├── @PutMapping │ │ ├── @DeleteMapping │ │ └── @PatchMapping │ └── @CrossOrigin（跨域） ├── 请求参数绑定 │ ├── @RequestParam │ ├── @PathVariable（路径变量） │ ├── @RequestHeader │ ├── @CookieValue │ ├── @MatrixVariable │ ├── @RequestBody（JSON/XML 等） │ ├── @ModelAttribute（表单/查询参数绑定到对象） │ ├── HttpServletRequest / HttpServletResponse │ └── MultipartFile（文件上传） ├── 返回类型 │ ├── String（视图名，配合 Model） │ ├── ModelAndView │ ├── void（直接操作 HttpServletResponse） │ ├── @ResponseBody / ResponseEntity（JSON/XML 响应） │ └── View、Model、Map 等 └── 异步处理 ├── Callable<T> ├── DeferredResult<T> └── ResponseBodyEmitter / SseEmitter / StreamingResponseBody四、数据绑定、类型转换与校验数据绑定与校验 ├── 类型转换 │ ├── PropertyEditor │ ├── Converter / ConverterFactory │ ├── Formatter（日期/数字等格式化） │ └── @InitBinder（自定义绑定规则，注册编辑器/校验器） ├── 数据校验 │ ├── JSR-380（Bean Validation）：@Valid / @Validated │ ├── BindingResult / Errors（校验结果） │ └── 自定义校验注解 └── WebDataBinder └── 控制绑定行为（允许/禁止字段等）五、RESTful Web 服务REST 支持 ├── @RestController = @Controller + @ResponseBody ├── HTTP 方法映射注解 ├── 内容协商 ContentNegotiationManager ├── HttpMessageConverter 消息转换器 │ ├── MappingJackson2HttpMessageConverter（JSON） │ ├── Jaxb2RootElementHttpMessageConverter（XML） │ ├── StringHttpMessageConverter │ └── FormHttpMessageConverter 等 ├── 请求/响应体处理 │ ├── @RequestBody │ └── @ResponseBody / ResponseEntity / HttpEntity ├── 跨域 @CrossOrigin / 全局 CORS 配置 └── HATEOAS 支持（Spring HATEOAS）六、视图解析与渲染视图技术 ├── JSP + JSTL │ └── InternalResourceViewResolver 配置 ├── Thymeleaf │ └── SpringTemplateEngine + ThymeleafViewResolver ├── FreeMarker │ └── FreeMarkerViewResolver ├── 重定向与转发 │ ├── "redirect:/path"（RedirectView） │ └── "forward:/path"（InternalResourceView） ├── 视图解析链（多个 ViewResolver 顺序解析） └── ContentNegotiatingViewResolver（根据请求类型选择视图）七、拦截器与过滤器拦截器 HandlerInterceptor（Spring MVC 层面） ├── 方法 │ ├── preHandle（控制器执行前） │ ├── postHandle（控制器执行后，视图渲染前） │ └── afterCompletion（视图渲染后） ├── 配置方式 │ ├── XML 配置 <mvc:interceptors> │ └── Java 配置实现 WebMvcConfigurer.addInterceptors() └── 与 Servlet Filter 的区别 ├── Filter 在 Servlet 容器层面，不依赖 Spring └── Interceptor 可访问 Handler 对象、ModelAndView，更贴合 MVC八、异常处理异常处理体系 ├── @ExceptionHandler（方法级，仅处理当前 Controller） ├── @ControllerAdvice / @RestControllerAdvice（全局异常处理） │ └── + @ExceptionHandler、@InitBinder、@ModelAttribute ├── HandlerExceptionResolver 接口 │ ├── SimpleMappingExceptionResolver（映射异常到错误视图） │ ├── DefaultHandlerExceptionResolver（处理标准 Spring 异常） │ └── ResponseStatusExceptionResolver ├── @ResponseStatus（标注异常，返回对应 HTTP 状态码） └── 自定义异常 + 全局处理九、文件上传与下载文件操作 ├── 上传 │ ├── MultipartResolver │ │ ├── CommonsMultipartResolver（需 Apache Commons FileUpload） │ │ └── StandardServletMultipartResolver（Servlet 3.0+ 标准） │ └── @RequestParam("file") MultipartFile ├── 下载 │ ├── ResponseEntity<byte[]> 配合 InputStreamResource │ ├── StreamingResponseBody（流式大文件下载） │ └── 设置 Content-Disposition 响应头 └── 多文件上传（MultipartFile[] 或 List<MultipartFile>）十、国际化与主题i18n & 主题 ├── MessageSource（国际化消息源） │ ├── ResourceBundleMessageSource │ └── ReloadableResourceBundleMessageSource（可热加载） ├── LocaleResolver（区域解析器） │ ├── AcceptHeaderLocaleResolver │ ├── SessionLocaleResolver │ ├── CookieLocaleResolver │ └── FixedLocaleResolver ├── LocaleChangeInterceptor（切换语言拦截器） ├── 主题 Theme │ ├── ThemeResolver │ └── ThemeSource（ResourceBundleThemeSource）十一、配置方式配置方式演进 ├── XML 时代 │ ├── web.xml 配置 DispatcherServlet │ ├── [name]-servlet.xml 配置 │ ├── <mvc:annotation-driven/> │ ├── <mvc:resources>（静态资源处理） │ └── <mvc:view-controller>（无逻辑视图跳转） ├── Servlet 3.0+ 纯 Java 配置 │ ├── WebApplicationInitializer / AbstractAnnotationConfigDispatcherServletInitializer │ ├── @Configuration + @EnableWebMvc │ ├── 实现 WebMvcConfigurer 接口进行定制 │ └── 静态资源 addResourceHandlers() └── Spring Boot 自动配置 ├── spring-boot-starter-web ├── 自动配置 DispatcherServlet、HttpMessageConverters、ViewResolver 等 ├── application.properties / yml 微调 └── 使用 @Configuration + WebMvcConfigurer 扩展十二、测试支持测试 ├── MockMvc（独立测试控制器，不启动完整服务器） │ ├── mockMvc.perform(get("/api")) │ ├── .andExpect(status().isOk()) │ └── .andExpect(jsonPath("$.name").value("test")) ├── @WebMvcTest（Spring Boot 切片测试，仅加载 MVC 层） ├── TestRestTemplate / WebTestClient（集成测试） └── Mock 服务依赖（@MockBean）十三、高级与周边高级特性 ├── CORS 全局配置（WebMvcConfigurer.addCorsMappings） ├── HTTP 缓存控制（CacheControl, ETag, Last-Modified） ├── Servlet 异步支持 / 响应式桥梁 ├── WebSocket 集成（@MessageMapping, SimpMessagingTemplate） ├── Swagger / OpenAPI 3 文档生成 ├── 多版本 API 管理 └── 自定义 HandlerMethodArgumentResolver / HandlerMethodReturnValueHandler如果需要某个分支更详细的逐层展开，或想看 Spring MVC 与 Spring Boot 整合的实例脑图，可以告诉我。
Spring MVC 请求生命周期SpringBoot 2.7 + Java8 企业级开发一、整体架构图 —— 一次请求的完整流水线HTTP Request -> http://你的项目/getUser?id=1 │ ▼ ┌─────────────────────┐ │ Tomcat │ 内嵌 Servlet 容器，最早接收请求 │ Servlet Container │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Filter 过滤器链 │ Servlet 级别的“门卫”，工作在 Spring MVC 之前 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DispatcherServlet │ Spring MVC 的“总控”，分发请求的核心 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ HandlerInterceptor │ Spring 自己的拦截器，更懂 Controller │ preHandle │ 请求到达 Controller 前执行 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Controller │ 你写的接口，@RestController / @GetMapping └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Service │ 业务逻辑层 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DAO/JdbcTemplate │ 数据访问层，执行 SQL └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ MySQL │ 数据库 └─────────────────────┘ │ ▼ Controller返回结果 返回 Java 对象 │ ▼ Interceptor.postHandle 所有拦截器的 postHandle │ ▼ ViewResolver / JSON序列化 前后端分离时转成 JSON 字符串 │ ▼ Interceptor.afterCompletion 资源清理、日志记录 │ ▼ Response → 浏览器二、企业项目真实执行顺序（必背）虚假的顺序：Tomcat -> Interceptor -> Controller实际严格顺序：Tomcat -> Filter -> DispatcherServlet -> Interceptor -> Controller -> Service -> DAO -> MySQL为什么是这个顺序？Filter 是 Servlet 规范 定义的，工作在容器级别，比 Spring 更底层；Interceptor 是 Spring MVC 内部 的，只有在 DispatcherServlet 接收请求之后才生效。也就是说：Filter 可以拦截所有请求（包括静态资源），而 Interceptor 只能拦截进入 Spring MVC 的请求。三、Filter 和 Interceptor 区别 —— 两个不同级别的“保安”1. Filter（Servlet 级别）定义方式：实现 javax.servlet.Filter 接口执行时机：Tomcat → Filter → Spring MVC特点：工作在容器层，不依赖 Spring，拿不到 Controller、方法等 Spring 上下文信息。典型用途：JWT 登录校验（直接解析 token，失败直接返回 401）XSS 过滤（清洗 等危险字符）请求日志（记录每个请求的耗时、URL）跨域处理（CorsFilter 统一设置响应头）2. Interceptor（Spring MVC 级别）定义方式：实现 org.springframework.web.servlet.HandlerInterceptor 接口执行时机：DispatcherServlet → Interceptor → Controller特点：能获取到 Handler（Controller 方法）、参数、返回结果等，是 Spring 生态内的拦截手段。典型用途：用户信息注入（从 token 解析用户信息，放入 UserContext）权限校验（admin / user 角色判断）操作日志（记录“谁在何时做了什么”）API 访问统计（PV / UV 计数）面试标准答案Filter 先执行，Interceptor 后执行。 Filter 是 Servlet 规范，Intercetpor 是 Spring MVC 专属。四、DispatcherServlet 到底干了什么 —— 核心方法 doDispatch()设计模式：前端控制器模式（Front Controller Pattern）所有请求都会先被 DispatcherServlet 接管，它自己不干活，只负责指挥。源码简化流程：doDispatch() { // 1. 根据请求找到 Handler（Controller 方法）并包装成 HandlerExecutionChain HandlerExecutionChain chain = getHandler(request); // 2. 执行所有拦截器的 preHandle chain.applyPreHandle(request, response); // 3. 真正调用 Controller 方法 HandlerAdapter adapter = getHandlerAdapter(handler); ModelAndView mv = adapter.handle(request, response, handler); // 4. 执行所有拦截器的 postHandle chain.applyPostHandle(request, response, mv); // 5. 处理返回结果（JSON 序列化、视图渲染等） processDispatchResult(request, response, chain, mv, exception); // 6. 触发拦截器的 afterCompletion（总是执行） chain.triggerAfterCompletion(request, response, exception); }五、HandlerExecutionChain —— Controller + 拦截器的组合体请求到达 DispatcherServlet 后，getHandler() 方法会将你的 Controller 方法和所有适用的拦截器打包成一个执行链。结构示意：HandlerExecutionChain ├── Handler（你的 Controller 方法） ├── Interceptor A ├── Interceptor B └── Interceptor C执行顺序（链式调用，具有栈特性）：进入时：A.pre → B.pre → C.pre → Controller返回时：C.post → B.post → A.post最终完成：C.after → B.after → A.after（即使前面出异常也会执行）六、@RequestScope 的底层原理 —— 代理对象，而非每次都 new很多人以为 @RequestScope 每次请求都会创建一个全新的 Bean，实际上 Spring 注入的是一个代理对象。特别提醒：代理对象通过当前线程的 ThreadLocal 拿到绑定的请求上下文，从而找到属于当前请求的那个真实 @RequestScope 实例。如果在 Controller 里用 @Async 等开了新线程，原请求的 ThreadLocal 会丢失，导致代理找不到请求上下文而直接报错。@Autowired private UserContext userContext;实际注入的是 UserContext$$SpringProxy 代理类。该代理对象内部并不持有真实数据，当真正调用其方法时，它会从当前请求的 RequestAttributes 中动态获取属于当前请求的真实实例。流程：当前线程 → RequestAttributes → 真实的 UserContext好处：即便 Controller 是单例（Singleton），也能安全地使用 @RequestScope 的 Bean，因为每次调用都会通过代理去获取当前请求对应的真实对象。七、ThreadLocal 与 @RequestScope —— 如何安全地存放用户信息在企业项目中，经常需要在任何地方（Service、DAO）获取当前登录用户，常用两种方案。方案实现方式优点缺点最佳实践ThreadLocalUserHolder.set(user) / UserHolder.get()极快，线程隔离必须手动 remove()，否则线程池复用时会内存泄漏或数据串人在拦截器的 afterCompletion 中清理@RequestScope注解在 Spring 管理的 Bean 上Spring 自动管理生命周期，请求结束自动销毁，无泄漏风险性能比 ThreadLocal 多一点点反射/代理开销优先推荐，简单安全推荐：存放用户上下文信息时，优先选择 @RequestScope。只有在对性能要求极高、并发极大的场景下，才考虑 ThreadLocal，并确保在拦截器的 afterCompletion 中执行 remove()。八、Controller 参数解析过程 —— 为什么参数能自动绑定你写的 public Result get(Long id)，Spring 能自动从请求里拿到 id，靠的是 HandlerMethodArgumentResolver 接口。该解析器会遍历判断当前参数是否能被自己解析，若能则从 request 中取值并绑定。常见注解与对应解析器：注解解析器@RequestParamRequestParamMethodArgumentResolver@PathVariablePathVariableMethodArgumentResolver@RequestBodyRequestResponseBodyMethodProcessor@RequestHeaderRequestHeaderMethodArgumentResolver例如：@RequestHeader("token") String token 背后就是 RequestHeaderMethodArgumentResolver 在干活。你也可以实现该接口并注册，来支持自定义的参数注入（比如自动注入当前登录用户对象）。九、JSON 如何变成 Java 对象 —— HttpMessageConverter前端发送 JSON：POST { "name":"Tom" }到后端自动变成 UserDTO 对象，核心组件是 HttpMessageConverter。默认使用的实现是 MappingJackson2HttpMessageConverter，底层依赖 Jackson。请求方向：请求体 JSON → Jackson 反序列化 → Java 对象 → Controller 参数响应方向：Controller 返回的 Java 对象 → Jackson 序列化 → JSON 字符串 → HTTP 响应因此 Jackson 是 Spring Boot 中处理 JSON 的事实标准，排查问题时经常要关注它。十、统一异常处理 —— 别再到处 try-catch在企业项目中，应使用 @RestControllerAdvice 进行全局异常拦截，避免在每个 Controller 方法中写重复的 try-catch。@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常", e); return Result.fail(e.getMessage()); } @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } }执行流程：Controller 抛出异常 ↓ HandlerExceptionResolver 捕获 ↓ 找到匹配的 @ExceptionHandler 方法 ↓ 返回统一的错误响应这样 Controller 只需关注正常业务逻辑，所有异常都会流入全局处理器，返回规范的接口格式。十一、完整认证流程 —— 不要只相信 Token真正的安全认证绝不能只解析 token，而必须结合 Redis 等存储做二次校验。标准流程：1. 请求携带 JWT token 2. Interceptor 解析 token 得到 userId 3. 去 Redis 查询该 userId 对应的完整用户信息（含权限、状态） 4. 如果 Redis 中不存在或 token 已失效 → 直接返回 401 5. 将用户信息放入 UserContext（@RequestScope 管理的 Bean） 6. Controller 直接从 UserContext 获取用户信息，无需再解析 token为什么要加 Redis？Token 可能被伪造（弱签名或密钥泄露）用户可能已被管理员禁用，但 token 还未过期用户执行了“退出登录”，token 理应立刻失效总结：Token 只负责提供身份标识（userId），真正的用户资料和权限应从 Redis（或数据库）获取，并在请求上下文中传递。十二、Spring MVC 八大核心组件（高级理解）DispatcherServlet 只是总控，真正复杂的工作由这些组件完成：组件作用重要性HandlerMapping根据请求 URL 找到对应的 Handler（Controller 方法）★★★HandlerAdapter真正执行 Handler，适配不同类型的处理器★★★HandlerExceptionResolver处理异常，分发给 @ExceptionHandler 或 @ControllerAdvice★★★ViewResolver解析视图名，找到具体视图（前后端分离时用得少）★MultipartResolver处理文件上传请求★LocaleResolver国际化，解析客户端的语言/地区★ThemeResolver主题切换（老旧项目中常见）★FlashMapManager用于重定向时保存临时数据★其中最核心的三个：HandlerMapping、HandlerAdapter、HandlerExceptionResolver，理解它们才能真正把握 Spring MVC 的执行原理。总结 —— 一条线全部串起来完整请求处理链路：Tomcat → Filter（登录校验、XSS、日志、跨域） → DispatcherServlet → HandlerMapping（找到 Controller 方法） → HandlerInterceptor.preHandle（权限、用户注入） → HandlerAdapter（执行方法） → Controller → Service → DAO / JdbcTemplate → MySQL → （若有异常）HandlerExceptionResolver → @RestControllerAdvice 统一处理 → HttpMessageConverter（Java 对象 ↔ JSON） → HandlerInterceptor.postHandle / afterCompletion → 返回 Response日常企业开发（SpringBoot 2.7 + JdbcTemplate + MySQL + Redis + JWT）中最核心的10个知识点：Filter — 容器层拦截，适合安全校验、跨域、日志Interceptor — Spring MVC 拦截，适合用户注入、权限、统计@RequestScope — 请求作用域 Bean，底层用代理实现ThreadLocal — 线程隔离的上下文存储，需手动清理HandlerMethodArgumentResolver — 参数解析器，实现自动注入HttpMessageConverter — JSON / 对象互转，底层 Jackson@RestControllerAdvice — 全局异常处理，不要到处 try-catchDispatcherServlet — 前端总控制器，调度所有请求HandlerExecutionChain — 拦截器与 Controller 的打包执行链Spring MVC 九大组件 — 理解项目架构离不开这些支撑组件
1. 策略模式 ⭐⭐⭐⭐⭐消灭 if-else 的核心武器，基于 Spring 容器自动选择策略。步骤：定义策略接口public interface Strategy { String getType(); void execute(); }实现多个策略并用 @Component 注册@Component public class StrategyA implements Strategy { @Override public String getType() { return "A"; } @Override public void execute() { /* ... */ } } @Component public class StrategyB implements Strategy { @Override public String getType() { return "B"; } @Override public void execute() { /* ... */ } }创建策略选择器@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 4. Spring自动注入所有Strategy实现到Map（详见文档末尾“注入条件”） public StrategySelector(Map<String, Strategy> strategyMap) { // 遍历注入的Map，按业务key重组 strategyMap.values().forEach(s -> this.strategyMap.put(s.getType(), s)); } // 5. 选择策略 public Strategy getStrategy(String type) { Strategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("No strategy for: " + type); } return strategy; } }2. 工厂模式 ⭐⭐⭐⭐⭐创建对象的“万能工厂”，通过 Spring 容器自动装配产品族。步骤：定义产品接口public interface Product { String getType(); void doSomething(); }多个产品实现并注册@Component public class ProductA implements Product { @Override public String getType() { return "A"; } @Override public void doSomething() { /*...*/ } } @Component public class ProductB implements Product { @Override public String getType() { return "B"; } @Override public void doSomething() { /*...*/ } }创建工厂类（Spring自动注入所有产品）@Component public class ProductFactory { private final Map<String, Product> productMap; // 构造器注入：Spring会将所有Product的实现注入到Map中（key=bean名称，默认类名首字母小写） public ProductFactory(Map<String, Product> productMap) { this.productMap = productMap; } public Product create(String type) { Product product = productMap.get(type); // 或自定义getType()映射 if (product == null) throw new IllegalArgumentException("Unknown product type"); return product; } }与策略模式的区别：工厂侧重于创建/获取对象，策略侧重行为互换，但 Spring 集成写法相似。3. 责任链模式 ⭐⭐⭐⭐⭐多个处理器依次尝试，直到有人处理。步骤：定义请求上下文public class RequestContext { private boolean approved = false; private String rejectReason; private String approvedBy; // getters & setters... }编写 Handler 接口public interface Handler { boolean handle(RequestContext context); // true=处理完毕，不再传递 }具体处理器（@Component + @Order 控制顺序）@Component @Order(1) public class ManagerHandler implements Handler { public boolean handle(RequestContext context) { // 逻辑：通过则设置approved=true，返回true；否则返回false return true; } } @Component @Order(2) public class DirectorHandler implements Handler { /* ... */ }组装责任链（@PostConstruct 确保只排序一次）@Component public class HandlerChain { private final List<Handler> handlers; public HandlerChain(List<Handler> handlers) { this.handlers = handlers; } @PostConstruct public void init() { AnnotationAwareOrderComparator.sort(handlers); } public void process(RequestContext context) { for (Handler handler : handlers) { if (handler.handle(context)) break; } } }使用@Autowired HandlerChain chain; // ... RequestContext ctx = new RequestContext(); chain.process(ctx);4. 模板方法模式 ⭐⭐⭐⭐⭐父类定义骨架，子类填写细节。步骤：抽象模板类public abstract class AbstractExportService { public final void export() { prepareData(); writeFile(); cleanUp(); } protected abstract void prepareData(); protected abstract void writeFile(); protected void cleanUp() { /* 钩子：默认空实现 */ } }具体子类@Component public class ExcelExportService extends AbstractExportService { @Override protected void prepareData() { /*...*/ } @Override protected void writeFile() { /*...*/ } }Controller中使用（注入所有子类）@RestController public class ExportController { private final Map<String, AbstractExportService> exportServiceMap; // 注入时 key 为 bean 名称（类名首字母小写，或用@Service("excel")指定） public ExportController(Map<String, AbstractExportService> exportServiceMap) { this.exportServiceMap = exportServiceMap; } @GetMapping("/export/{type}") public void doExport(@PathVariable String type) { exportServiceMap.get(type).export(); } }5. 代理模式 ⭐⭐⭐⭐⭐控制对象访问，增强功能（Spring AOP 的基础）。Spring 推荐方式：声明式：@Transactional、@Cacheable 等本质是动态代理。手动示例：统计方法耗时。@Component public class SimpleService implements IService { @Override public void work() { /* ... */ } } @Component public class ServiceProxy implements IService { private final IService target; public ServiceProxy(@Qualifier("simpleService") IService target) { this.target = target; } @Override public void work() { long start = System.currentTimeMillis(); target.work(); System.out.println("cost: " + (System.currentTimeMillis() - start)); } }更常用的是直接使用 Spring AOP（@Aspect + @Around）实现动态代理。6. 状态模式 ⭐⭐⭐⭐⭐将状态逻辑拆分为独立的状态对象，避免大量 if-else。步骤（订单状态机）：状态接口public interface OrderState { void pay(OrderContext ctx); void ship(OrderContext ctx); }具体状态@Component public class PendingPaymentState implements OrderState { public void pay(OrderContext ctx) { // 支付成功后切换状态 ctx.setState(ctx.getPaidState()); } public void ship(OrderContext ctx) { throw new UnsupportedOperationException(); } } // PaidState, ShippedState... 同理上下文持有状态@Component public class OrderContext { private OrderState current; // 注入所有状态 @Autowired private PendingPaymentState pendingPaymentState; @Autowired private PaidState paidState; //... getters/setters，初始化为 pendingPaymentState }Spring 中可使用状态枚举配合 Map<OrderStatus, OrderState> 自动注入。7. 观察者模式 ⭐⭐⭐⭐一对多通知，Spring 事件机制天然支持。步骤：定义事件public class OrderCreatedEvent extends ApplicationEvent { private final Long orderId; public OrderCreatedEvent(Object source, Long orderId) { super(source); this.orderId = orderId; } // getter }发布事件@Component public class OrderService { @Autowired private ApplicationEventPublisher publisher; public void createOrder() { // 业务... publisher.publishEvent(new OrderCreatedEvent(this, orderId)); } }监听事件（可使用 @EventListener 或 @TransactionalEventListener）@Component public class SendSmsListener { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // 发短信 } }8. 门面模式 ⭐⭐⭐⭐为复杂子系统提供统一入口，Service 层常见模式。示例： 下单门面聚合库存、支付、物流三个服务。@Component public class OrderFacade { @Autowired private InventoryService inventoryService; @Autowired private PaymentService paymentService; @Autowired private LogisticsService logisticsService; public void placeOrder(OrderDTO dto) { inventoryService.reduceStock(dto.getSku()); paymentService.charge(dto.getAmount()); logisticsService.scheduleDelivery(dto.getOrderId()); } }9. 装饰器模式 ⭐⭐⭐⭐动态附加责任，比继承更灵活。示例： 对数据读写组件增加缓存装饰器。public interface DataService { String read(); } @Component("basicDataService") public class BasicDataService implements DataService { /*...*/ } @Component public class CacheDecorator implements DataService { private final DataService delegate; public CacheDecorator(@Qualifier("basicDataService") DataService delegate) { this.delegate = delegate; } @Override public String read() { // 先查缓存，没有则调用delegate并缓存 return delegate.read(); } }10. 适配器模式 ⭐⭐⭐⭐兼容两个不匹配的接口，常用于整合外部系统。// 目标接口 public interface Logger { void log(String msg); } // 被适配的第三方类 public class ExternalLogLib { public void writeLog(String text) { /*...*/ } } // 适配器 @Component public class ExternalLogAdapter implements Logger { @Autowired private ExternalLogLib externalLogLib; @Override public void log(String msg) { externalLogLib.writeLog(msg); } }11. DDD（领域驱动设计） ⭐⭐⭐⭐⭐⭐聚焦核心业务复杂度，分层架构与战略设计。核心步骤（简化分层）：用户接口层：Controller，接收请求并调用应用服务。应用层：@Service 应用服务，编排领域对象，不包含业务逻辑。领域层：实体（Entity）、值对象（Value Object）、聚合根（Aggregate Root）、领域服务（Domain Service）、仓储接口（Repository Interface）。基础设施层：仓储实现（@Repository），将聚合持久化到数据库。关键原则：领域层无任何 Spring 注解，纯 POJO。通过构造器注入将仓储接口注入领域服务或应用服务。聚合内通过根实体确保一致性。12. Event Driven（事件驱动架构） ⭐⭐⭐⭐⭐⭐通过事件解耦服务，提升伸缩性和最终一致性。Spring 实现：本地事件：ApplicationEventPublisher + @EventListener（见观察者模式）。分布式事件：集成 Kafka / RabbitMQ。// 发布领域事件到消息队列 @Autowired private KafkaTemplate<String, DomainEvent> kafkaTemplate; public void publish(DomainEvent event) { kafkaTemplate.send("topic-name", event); } // 消费 @KafkaListener(topics = "topic-name") public void handle(DomainEvent event) { /*...*/ }推荐配合 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 保证事务提交后再发消息。13. Saga ⭐⭐⭐⭐⭐⭐分布式事务的最终一致性方案，通过协调正向/补偿操作。编排型 Saga（借助事件驱动）：每个微服务监听前序事件，执行本地事务并发布新事件。若失败，发布补偿事件，上游监听并回滚。// 服务A发布订单创建事件，服务B监听并扣库存 // 若扣库存失败，服务B发布库存不足事件，服务A监听后进行补偿（取消订单）实现时可配合 Spring Cloud Stream / Axon Framework。14. CQRS（命令查询职责分离） ⭐⭐⭐⭐⭐⭐读写模型分离，最大化性能与扩展性。简单实现：命令侧：应用服务接收 Command，调用领域逻辑，写入主库。查询侧：@Service 直接从读库或缓存查询 DTO。// 命令 @PostMapping public void createOrder(@RequestBody CreateOrderCmd cmd) { commandService.handle(cmd); } // 查询（独立Controller或同一模块内分离） @GetMapping("/orders/{id}") public OrderView queryOrder(@PathVariable Long id) { return queryService.findById(id); }高级玩法：事件溯源，将所有状态变更存储为事件流，查询视图由事件投影构建。SpringBoot 2.7 注入条件（集合/Map注入）核心行为： Spring 会自动将所有实现了某个接口或父类的 Bean 注入到 List 或 Map 中。注入 List： @Autowired List<SomeInterface> list; 会按 Order 排序包含所有实现类的 Bean。注入 Map： @Autowired Map<String, SomeInterface> map; key 为 Bean 的名称（默认类名首字母小写，若用 @Service("myName") 则用指定名称），value 为实例。没有实现类时： Spring 默认会注入一个空集合（EmptyList/EmptyMap），不会报错。若希望强制存在才注入，可使用 @Autowired(required = true)，但注入集合时 required=true 仍允许空集合，除非你将它用在非集合字段上。推荐方式： 构造器注入，显式声明 final 字段，避免 @Autowired 注解（Spring 4.3+ 单构造器自动注入）。@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 构造器注入：Spring 自动将所有的 Strategy 实现类注入 Map public StrategySelector(Map<String, Strategy> allStrategies) { allStrategies.values().forEach(s -> strategyMap.put(s.getType(), s)); } }在 Spring Boot 2.7 (Spring Framework 5.3.x) 下此行为完全稳定，完美支持策略/工厂等模式的自动装配。
反射 + AOP + 注解 讲解目录注解（Annotation）什么是注解元注解与自定义注解如何在反射中读取注解反射（Reflection）反射的核心类：Class、Method、Field动态创建对象、调用方法、访问字段反射与注解的结合AOP（面向切面编程）AOP 的核心概念Spring AOP 的实现原理（代理）通知类型与切入点表达式如何在切面中使用反射和注解三剑合璧实战案例案例1：自定义 @Log 日志记录案例2：自定义 @RequirePermission 权限控制总结与关系图谱一、注解（Annotation）1.1 什么是注解？注解是 Java 5 引入的一种元数据，它本身不影响代码逻辑，但可以被编译、运行时工具或框架读取，从而赋予代码额外的语义或行为。常见注解如 @Override、@Deprecated、@Autowired 等。1.2 元注解定义注解时需要用到的注解，共有四个：元注解作用@Target指定注解能用在哪里（类、方法、字段等）@Retention指定注解的存活周期（源码、字节码、运行时）@Documented是否包含在 JavaDoc 中@Inherited是否允许子类继承父类的注解@Retention 的取值：RetentionPolicy.SOURCE：只在源码中存在，编译时丢弃（如 @Override）RetentionPolicy.CLASS：在字节码中存在，但运行时不可见（默认）RetentionPolicy.RUNTIME：运行时可通过反射读取（自定义注解必须选这个）1.3 自定义一个注解import java.lang.annotation.*; @Target(ElementType.METHOD) // 只能用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留 public @interface Loggable { String value() default ""; // 注解属性 }使用：@Loggable("删除用户") public void deleteUser(Long id) { ... }1.4 在反射中读取注解无论是类、方法还是字段，反射 API 都提供了读取注解的方法：getAnnotation(Class<T> annotationClass) —— 获取指定类型的注解getAnnotations() —— 获取所有注解（含继承）getDeclaredAnnotations() —— 获取直接声明的注解（不含继承）isAnnotationPresent(Class<?> annotationClass) —— 判断是否存在某注解示例：读取方法上的 @LoggableMethod method = ...; // 通过反射拿到方法对象 Loggable log = method.getAnnotation(Loggable.class); if (log != null) { System.out.println("描述：" + log.value()); }二、反射（Reflection）2.1 反射的核心类类作用获取方式Class类的元信息对象.getClass()、类名.class、Class.forName("全类名")Method方法信息Class 对象的 getMethod(...)、getDeclaredMethods() 等Field字段信息Class 对象的 getField(...)、getDeclaredFields() 等Constructor构造器信息Class 对象的 getConstructor(...)、getDeclaredConstructors() 等2.2 获取 Class 对象的三种方式// 1. 通过类名.class Class<String> clz1 = String.class; // 2. 通过实例的 getClass() String s = "hello"; Class<?> clz2 = s.getClass(); // 3. 通过完全限定名字符串 Class<?> clz3 = Class.forName("java.lang.String");2.3 获取 Method 对象方法说明getMethod(name, paramTypes...)获取指定名称和参数的 public 方法（含继承）getMethods()获取所有 public 方法（含继承）getDeclaredMethod(name, paramTypes...)获取本类中声明的方法（不含继承，可访问私有）getDeclaredMethods()获取本类中声明的所有方法（不含继承）示例：获取 deleteUser(Long) 方法Method method = UserService.class.getMethod("deleteUser", Long.class);2.4 动态调用方法UserService target = new UserService(); Method method = UserService.class.getMethod("deleteUser", Long.class); Object result = method.invoke(target, 123L); // 相当于 target.deleteUser(123L)如果方法是私有的，需要先 setAccessible(true)。2.5 反射与注解的结合反射的 Method、Field、Class 等都实现了 AnnotatedElement 接口，因此可以直接读取注解。Method method = ...; if (method.isAnnotationPresent(Loggable.class)) { Loggable log = method.getAnnotation(Loggable.class); System.out.println("需要记录日志：" + log.value()); }应用场景：框架（Spring、Hibernate）通过反射扫描注解，完成依赖注入、ORM 映射等。自定义工具类，根据注解动态生成 SQL、校验参数等。三、AOP（面向切面编程）3.1 AOP 核心概念切面（Aspect）：封装横切逻辑的模块（比如日志、权限）。连接点（JoinPoint）：程序执行过程中的一个点（方法调用、字段访问等），Spring AOP 只支持方法连接点。切入点（Pointcut）：匹配连接点的表达式，决定哪些方法会被增强。通知（Advice）：在切入点处执行的动作（具体逻辑）。目标对象（Target）：被代理的原始对象。代理（Proxy）：包裹目标对象，加入切面逻辑后生成的对象。3.2 Spring AOP 的实现Spring AOP 基于动态代理：如果目标类实现了接口，默认使用 JDK 动态代理（java.lang.reflect.Proxy）。如果没有接口，则使用 CGLIB 生成子类作为代理。从 Spring Boot 2.0 开始，默认使用 CGLIB 代理（即使有接口也会用 CGLIB）。3.3 通知类型与切入点表达式通知类型注解说明@Before目标方法执行前@AfterReturning目标方法正常返回后@AfterThrowing目标方法抛出异常后@After目标方法结束后（类似 finally）@Around包围目标方法，可控制是否执行常用切入点表达式表达式含义execution(* com.example.service.*.*(..))匹配 service 包下的所有方法@annotation(com.example.Loggable)匹配带有 @Loggable 注解的方法execution(public * *(..))匹配所有 public 方法within(com.example.service.*)匹配 service 包下的所有类的所有方法3.4 切面中如何使用反射和注解在 @Around 通知中，通过 ProceedingJoinPoint 可以获取方法签名（MethodSignature），然后通过反射拿到 Method 对象，进而读取注解。@Around("@annotation(loggable)") public Object log(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable { // 获得方法签名 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 反射 Method 对象 // 可以读取更多注解 AnotherAnnotation anno = method.getAnnotation(AnotherAnnotation.class); // 执行原方法 return pjp.proceed(); }为什么要 MethodSignature？ 因为 pjp.getSignature() 返回的是通用的 Signature 接口，只有强转成 MethodSignature 才能获取反射 Method 以及参数名、返回类型等详细信息。3.5 JoinPoint 信息树（速查）JoinPoint (ProceedingJoinPoint 继承它) ├─ getTarget() → 目标对象（被代理的原始 Bean） ├─ getThis() → 当前代理对象 ├─ getArgs() → 方法参数值数组 ├─ getSignature() → Signature (实际是 MethodSignature) │ ├─ getName() → 方法名 │ ├─ getDeclaringTypeName() → 声明该方法的类全名 │ └─ (MethodSignature 特有) │ ├─ getMethod() → 反射 Method ★ │ │ └─ getAnnotation(..) → 获取注解 │ ├─ getParameterNames() → 参数名数组（需编译配置） │ ├─ getReturnType() → 返回类型 │ └─ getParameterTypes() → 参数类型数组 └─ getStaticPart() → 同 getSignature()四、三剑合璧实战案例4.1 案例 1：@Log 注解实现方法日志记录需求：在方法执行时自动打印入参、出参和耗时。步骤：自定义 @Log 注解编写切面 LogAspect，使用 @Around + @annotation在切面中通过 MethodSignature 获取方法名，通过 getArgs() 获取参数值// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { } // 切面 @Aspect @Component public class LogAspect { @Around("@annotation(com.example.Log)") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); String method = sig.getDeclaringTypeName() + "." + sig.getName(); Object[] args = pjp.getArgs(); System.out.println(">>> 调用 " + method + "，参数：" + Arrays.toString(args)); long start = System.currentTimeMillis(); Object result = pjp.proceed(); long time = System.currentTimeMillis() - start; System.out.println("<<< 返回：" + result + " 耗时：" + time + "ms"); return result; } } // 业务使用 @Service public class UserService { @Log public String getUser(Long id) { return "User#" + id; } }4.2 案例 2：@RequirePermission 权限控制需求：标记方法需要某权限，没有则拒绝访问。实现思路：定义注解 @RequirePermission("user:delete")在拦截器中把用户权限存入 ThreadLocal切面中读取注解所需的权限值，与用户权限对比，不满足抛异常// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); } // 权限上下文 public class UserContext { private static final ThreadLocal<List<String>> PERMISSIONS = new ThreadLocal<>(); public static void set(List<String> p) { PERMISSIONS.set(p); } public static List<String> get() { return PERMISSIONS.get() != null ? PERMISSIONS.get() : Collections.emptyList(); } public static void clear() { PERMISSIONS.remove(); } } // 切面 @Aspect @Component public class PermissionAspect { @Around("@annotation(RequirePermission)") public Object check(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); Method method = sig.getMethod(); RequirePermission permission = method.getAnnotation(RequirePermission.class); String required = permission.value(); if (!UserContext.get().contains(required)) { throw new RuntimeException("无权限：" + required); } return pjp.proceed(); } } // 拦截器（简化） public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, ...) { String user = request.getHeader("X-User"); List<String> permissions = loadPermissions(user); // 从数据库查 UserContext.set(permissions); return true; } @Override public void afterCompletion(...) { UserContext.clear(); } }五、总结与关系图谱三者关系：注解 是标记，贴在代码上描述元数据。反射 是读取这些标记和操作代码结构（类、方法）的能力。AOP 是利用反射机制，在运行时动态地将横切逻辑织入到带有特定注解（或符合表达式）的方法周围。常见协作模式：自定义一个注解（如 @Log）在 AOP 切面中，通过切入点表达式 @annotation(Log) 拦截所有标注了该注解的方法在切面通知方法里，利用 JoinPoint/ProceedingJoinPoint 获取方法签名（MethodSignature）通过 MethodSignature.getMethod() 拿到反射 Method 对象，再用 getAnnotation() 读取注解的具体值，实现差异化逻辑学习路径建议：先熟练 反射（Class、Method、注解读取）理解 代理模式（JDK 动态代理、CGLIB）掌握 Spring AOP 的基本用法（切入点表达式、通知类型）最后将三者结合，实现自定义注解驱动切面当你遇到如下代码时，大脑能快速解析：MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); MyAnnotation ann = method.getAnnotation(MyAnnotation.class);它正在用 AOP 的连接点 获取 反射的 Method，然后 读取自定义注解，从而执行相应逻辑——这。
Spring JDBC + JdbcTemplate 学习笔记（方法驱动格式）本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍：方法：方法签名/名称作用：该方法解决什么问题参数：主要参数的含义和类型方法案例：代码示例（可直接运行）实例说明：对案例的详细解释，包括注意事项前置环境：Spring 6 + HikariCP + HSQLDB，配置了 DataSource 和 JdbcTemplate Bean。第一章：准备工作与环境配置1.1 Maven 依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.7.1</version> </dependency>1.2 Spring 配置类（AppConfig）@Configuration @ComponentScan @PropertySource("jdbc.properties") public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); return new HikariDataSource(config); } @Bean JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }1.3 数据库初始化（自动建表）@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" + "id BIGINT IDENTITY PRIMARY KEY, " + "email VARCHAR(100) NOT NULL UNIQUE, " + "password VARCHAR(100), " + "name VARCHAR(100))"); } }第二章：核心方法详解（按格式组织）方法一：queryForObject() – 查询单行记录并映射为对象作用：执行查询 SQL，期望返回一条记录，并将结果集的第一行通过 RowMapper 转换成 Java 对象。若查询结果为空或大于一行则抛出异常。参数：String sql – SQL 语句（可使用 ? 占位符）RowMapper<T> rowMapper – 将 ResultSet 当前行映射为 T 对象的函数式接口Object... args – 可变参数，按顺序替换 SQL 中的 ?方法案例：根据 email 查询用户public User getUserByEmail(String email) { String sql = "SELECT id, email, password, name FROM users WHERE email = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), email ); }实例说明：RowMapper 的 lambda 表达式接收 ResultSet 和当前行号（通常忽略行号），手动构建 User 对象。如果数据库中不存在该 email，会抛出 EmptyResultDataAccessException，需要捕获处理或改为返回 null。也适用于聚合查询，例如统计总数：public long countUsers() { String sql = "SELECT COUNT(*) FROM users"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getLong(1)); }方法二：query() – 查询多行记录，返回 List作用：执行查询 SQL，将结果集中的每一行都通过 RowMapper 转换为对象，最后返回 List<T>。结果集为空时返回空列表（不抛异常）。参数：String sql – SQL 语句RowMapper<T> rowMapper – 行映射器Object... args – 参数列表方法案例：分页查询用户public List<User> getUsersByPage(int pageIndex, int pageSize) { String sql = "SELECT * FROM users LIMIT ? OFFSET ?"; int offset = (pageIndex - 1) * pageSize; return jdbcTemplate.query(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), pageSize, offset ); }实例说明：使用 BeanPropertyRowMapper 可自动映射（要求列名与属性名一致或下划线转驼峰）：public List<User> getAllUsers() { return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class)); }BeanPropertyRowMapper 适合字段较多的简单映射；复杂映射仍需手动 RowMapper。方法三：update() – 执行插入、更新、删除作用：执行 DML 语句（INSERT/UPDATE/DELETE），返回受影响的行数。参数：String sql – DML 语句Object... args – 参数列表返回值：int – 受影响的行数方法案例 1：更新用户名称public int updateUserName(Long id, String newName) { String sql = "UPDATE users SET name = ? WHERE id = ?"; return jdbcTemplate.update(sql, newName, id); }实例说明：调用方可根据返回值判断是否更新成功（例如返回值 == 0 表示用户不存在）。方法案例 2：删除用户public boolean deleteUser(Long id) { String sql = "DELETE FROM users WHERE id = ?"; int rows = jdbcTemplate.update(sql, id); return rows > 0; }方法案例 3：插入用户（不返回主键）public void addUserWithoutKey(String name, String email) { String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, name, email); }方法四：update() + KeyHolder – 插入并返回自增主键作用：执行插入语句后，获取数据库自动生成的主键（如 IDENTITY 或 AUTO_INCREMENT）。参数：PreparedStatementCreator psc – 用于创建 PreparedStatement，必须指定 Statement.RETURN_GENERATED_KEYSKeyHolder keyHolder – 用于接收生成的主键方法案例：public User addUserAndGetId(String name, String email, String password) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, password); return ps; }, keyHolder); Long newId = keyHolder.getKey().longValue(); return new User(newId, email, password, name); }实例说明：PreparedStatementCreator 是一个函数式接口，需手动设置参数并返回带 RETURN_GENERATED_KEYS 的 PreparedStatement。执行后通过 keyHolder.getKey() 获取主键，通常为 Number 类型，需转换为 Long 或 Integer。适用于大多数关系型数据库（MySQL、HSQLDB、PostgreSQL、SQL Server 等）。方法五：batchUpdate() – 批量操作作用：一次性执行多条相同结构的 SQL（通常是批量插入或批量更新），性能远高于循环单条执行。参数：String sql – 带占位符的 SQL 模板List<Object[]> batchArgs – 每个元素是一个 Object[]，代表一次执行对应的参数返回值：int[] – 数组长度等于批处理次数，每个元素为对应操作影响的行数方法案例：批量插入多个用户public int[] batchAddUsers(List<User> users) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; List<Object[]> batch = new ArrayList<>(); for (User u : users) { batch.add(new Object[]{u.getName(), u.getEmail(), u.getPassword()}); } return jdbcTemplate.batchUpdate(sql, batch); }实例说明：批量操作会减少 JDBC 与数据库之间的网络往返次数，显著提升吞吐量。注意：某些数据库（如 HSQLDB）对单次批处理的大小有限制，可分批提交。方法六：execute() – 执行任意 SQL（DDL 或存储过程）作用：执行 DDL（如 CREATE TABLE）或任何不希望返回结果集的 SQL，也可以用于执行存储过程。参数：String sql – 要执行的 SQL 字符串返回值：无（或者可重载为 execute(Callback) 形式返回自定义结果）方法案例：创建日志表public void createLogTable() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sys_log (" + "id INT PRIMARY KEY, " + "message VARCHAR(255), " + "create_time TIMESTAMP)"); }实例说明：execute(String sql) 不关心返回结果，适合做数据库初始化、清空表、重建索引等管理操作。方法七：execute(ConnectionCallback) – 最底层扩展作用：获取原生 JDBC Connection 对象，执行任意复杂数据库操作（如设置超时、调用存储过程、使用原生 API 等）。参数：ConnectionCallback<T> action – 回调接口，接收 Connection 并返回 T返回值：由回调决定的 T方法案例：使用原生 PreparedStatement 和手动控制事务public User getUserWithNativeConnection(Long id) { return jdbcTemplate.execute((Connection conn) -> { try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setLong(1, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ); } return null; } } }); }实例说明：JdbcTemplate 会自动释放 Connection，但回调内创建的 PreparedStatement 和 ResultSet 必须由开发者手动释放（建议使用 try-with-resources）。一般情况下优先使用更高级的 query()、update() 等方法，只有非常规需求时才使用 ConnectionCallback。方法八：execute(PreparedStatementCallback) – 灵活处理 PreparedStatement作用：由 JdbcTemplate 创建 PreparedStatement，回调中可自行执行查询、更新并处理结果集。参数：String sql – SQL 模板PreparedStatementCallback<T> action – 回调接收 PreparedStatement，返回 T方法案例：判断用户是否存在public boolean existsUserByName(String name) { return jdbcTemplate.execute("SELECT 1 FROM users WHERE name = ?", (PreparedStatement ps) -> { ps.setString(1, name); try (ResultSet rs = ps.executeQuery()) { return rs.next(); // 有记录返回 true } }); }实例说明：此方法比 ConnectionCallback 更高层，无需创建 PreparedStatement，但依然需要手动执行查询和处理 ResultSet。第三章：RowMapper 详解（两个常用实现）3.1 手动 RowMapper（Lambda）作用：将 ResultSet 当前行的数据提取并组装成业务对象。格式：(ResultSet rs, int rowNum) -> { ... }案例：已在 queryForObject 中展示。3.2 BeanPropertyRowMapper作用：自动将结果集的列名（支持下划线转驼峰）映射到 JavaBean 的同名属性（需有 setter）。参数：BeanPropertyRowMapper.newInstance(Class<T>) 或构造函数。案例：List<User> users = jdbcTemplate.query("SELECT id, email, name FROM users", BeanPropertyRowMapper.newInstance(User.class));实例说明：要求数据库列名 email 对应 Java 属性 email；若列名为 user_name，则对应 userName。非常便捷，适合大多数查询场景。第四章：声明式事务管理核心注解：@Transactional作用：将方法（或类中的所有方法）标记为事务性方法。Spring 会在方法执行前开启事务，执行成功则提交，抛出未捕获异常则回滚。参数（常用）：propagation – 传播行为（默认 REQUIRED）isolation – 隔离级别（默认数据库默认级别）rollbackFor – 指定哪些异常触发回滚（默认 RuntimeException 和 Error）方法案例：银行转账（两个更新操作必须在同一事务）@Service public class AccountService { @Autowired JdbcTemplate jdbcTemplate; @Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId); // 模拟异常：如果转账金额过大，回滚两个更新 if (amount.compareTo(BigDecimal.valueOf(10000)) > 0) { throw new RuntimeException("转账金额超过限额"); } jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId); } }实例说明：需要先在配置类上添加 @EnableTransactionManagement 并注册 DataSourceTransactionManager。默认情况下，只有 RuntimeException 会触发回滚；若希望检查型异常也回滚，需设置 @Transactional(rollbackFor = Exception.class)。事务边界默认基于 AOP 代理，内部方法调用（this.method()）不会触发事务。第五章：实战示例 – RESTful API 完整流程5.1 User 实体public class User { private Long id; private String name; private String email; private String password; // getter/setter/构造方法 省略 }5.2 UserService（使用上述方法）@Service public class UserService { @Autowired JdbcTemplate jdbcTemplate; public User addUser(String name, String email, String pwd) { KeyHolder kh = new GeneratedKeyHolder(); jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement( "INSERT INTO users (name, email, password) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, pwd); return ps; }, kh); return new User(kh.getKey().longValue(), email, pwd, name); } public User getUser(Long id) { try { return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", BeanPropertyRowMapper.newInstance(User.class), id); } catch (EmptyResultDataAccessException e) { return null; } } public List<User> listUsers() { return jdbcTemplate.query("SELECT * FROM users", BeanPropertyRowMapper.newInstance(User.class)); } public int updateUser(Long id, String newName) { return jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id); } public int deleteUser(Long id) { return jdbcTemplate.update("DELETE FROM users WHERE id = ?", id); } }5.3 UserController@RestController @RequestMapping("/api/users") public class UserController { @Autowired UserService userService; @PostMapping public User add(@RequestParam String name, @RequestParam String email, @RequestParam String pwd) { return userService.addUser(name, email, pwd); } @GetMapping("/{id}") public User get(@PathVariable Long id) { return userService.getUser(id); } @GetMapping public List<User> list() { return userService.listUsers(); } @PutMapping("/{id}") public String update(@PathVariable Long id, @RequestParam String name) { int rows = userService.updateUser(id, name); return rows > 0 ? "success" : "user not found"; } @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { int rows = userService.deleteUser(id); return rows > 0 ? "deleted" : "not exist"; } }5.4 测试命令# 新增 curl -X POST "http://localhost:8080/api/users?name=张三&email=zs@test.com&pwd=123" # 查询单个 curl "http://localhost:8080/api/users/1" # 查询全部 curl "http://localhost:8080/api/users" # 更新 curl -X PUT "http://localhost:8080/api/users/1?name=张三丰" # 删除 curl -X DELETE "http://localhost:8080/api/users/1"第六章：常见问题与注意事项queryForObject 空结果抛异常：需捕获 EmptyResultDataAccessException 或改用 query 方法加判断。BeanPropertyRowMapper 要求属性有 setter：否则映射会忽略该列。批量插入时数据库限制：例如 HSQLDB 默认批量最大 100 条，可分批调用 batchUpdate。事务失效场景：同一个类内非 @Transactional 方法调用 @Transactional 方法（无代理）方法被 private 修饰异常被 try-catch 吞掉且没有重新抛出使用 KeyHolder 时：必须通过 Statement.RETURN_GENERATED_KEYS 创建 PreparedStatement。附录：JdbcTemplate 方法速查表方法名作用适用场景queryForObject单行，返回对象根据主键查询、聚合函数query多行，返回 List列表查询、分页update单条 DML插入/更新/删除update + KeyHolder插入并返回主键获取自增 IDbatchUpdate批量 DML批量导入数据execute(String)DDL/存储过程建表、清空表execute(ConnectionCallback)原生 JDBC 操作特殊需求、存储过程---。
注意：Java 8 环境下，javax.annotation（@PostConstruct、@PreDestroy、@Resource）是 JDK 自带的，无需额外添加依赖。Spring Boot 2.7 使用 Spring Framework 5.x，完美支持 JSR-250 规范。一、Bean 的作用域（Scope）Spring 中的 Bean 默认是 单例（Singleton），即整个容器内只有一个实例。也可以通过 @Scope 指定为 原型（Prototype），每次获取都创建新实例。1.1 Singleton（单例）—— 默认创建时机：容器启动时（除非设置为 @Lazy）。销毁时机：容器正常关闭时，会调用 @PreDestroy 方法。获取结果：每次注入或 getBean() 返回同一个对象。@Component // 默认 singleton public class MailService { // ... }1.2 Prototype（原型）行为：每次获取（@Autowired 或 getBean）都会创建一个全新的实例。生命周期差异：容器负责创建 → 依赖注入 → 调用 @PostConstruct。容器不负责销毁：即使你在原型 Bean 中写了 @PreDestroy，Spring 不会自动调用它。需要你自己写代码清理（例如手动调用销毁方法）。声明方式：@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或 @Scope("prototype") public class MailSession { @PostConstruct public void init() { System.out.println("原型 Bean 初始化，每次都会调用"); } @PreDestroy public void destroy() { System.out.println("原型 Bean 销毁 — 不会被 Spring 自动调用"); } }最佳实践：如果没有特殊需求，优先使用 singleton。prototype 适合那些有状态且不宜共享的对象，但要记得手动管理资源释放。二、注入 List —— 自动收集同一类型的所有 Bean当你需要在一个地方使用某个接口的 所有实现类 时，可以直接注入 List<T>，Spring 会自动把该接口的所有 Bean 放进列表。示例：多个 Validator 实现public interface Validator { void validate(String email, String password, String name); } @Component // 自动被收集 public class EmailValidator implements Validator { @Override public void validate(String email, String password, String name) { // email 校验逻辑 } } @Component public class PasswordValidator implements Validator { @Override public void validate(String email, String password, String name) { // password 校验逻辑 } } @Component public class NameValidator implements Validator { @Override public void validate(String email, String password, String name) { // name 校验逻辑 } }注入 List@Component public class Validators { @Autowired private List<Validator> validators; // Spring 自动注入所有 Validator 实现 public void validate(String email, String password, String name) { for (Validator v : validators) { v.validate(email, password, name); } } }控制顺序 —— @Order默认顺序不确定，可以用 @Order 注解指定（数字越小优先级越高）。@Component @Order(1) public class EmailValidator implements Validator { ... } @Component @Order(2) public class PasswordValidator implements Validator { ... } @Component @Order(3) public class NameValidator implements Validator { ... }注入的 List<Validator> 会按照 @Order 的值升序排列。三、可选注入 —— @Autowired(required = false)默认情况下，如果某个依赖的 Bean 不存在，Spring 会抛出 NoSuchBeanDefinitionException。如果你允许该依赖为 null，可以设置 required = false。@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); // 找不到 ZoneId 时使用默认值 }典型场景：某些配置是可选的（如时区、外部服务），若有定义则使用，否则退回到默认值。四、创建第三方 Bean —— @Bean 方法对于不在我们包内的类（例如 JDK 的 ZoneId、第三方库的 DataSource），无法用 @Component 扫描。此时需要在 @Configuration 类中编写一个 工厂方法，并标记 @Bean。@Configuration @ComponentScan public class AppConfig { @Bean public ZoneId zoneId() { return ZoneId.of("Z"); } @Bean("customRestTemplate") // 指定 Bean 名称 public RestTemplate myRestTemplate() { return new RestTemplate(); } }Spring 保证该方法 只被调用一次（默认单例）。方法名默认作为 Bean 的名称，也可用 @Bean("name") 显式命名。可以依赖其他 Bean：方法参数会自动注入。@Bean public DataSource dataSource() { return HikariDataSource(...); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { // 自动注入上面创建的 DataSource return new JdbcTemplate(dataSource); }本质上，@Bean 方法就是一种 工厂方法，比实现 FactoryBean 接口更简洁。五、Bean 的初始化和销毁 —— @PostConstruct 和 @PreDestroy5.1 为什么需要初始化：在依赖注入完成后，执行一些启动逻辑（打开连接、启动后台线程、校验配置）。销毁：在容器关闭前，释放资源（关闭连接池、停止线程、清理临时文件）。5.2 使用方式（JSR-250，Java 8 原生支持）@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); @PostConstruct public void init() { System.out.println("初始化：依赖已注入，zoneId = " + zoneId); } @PreDestroy public void shutdown() { System.out.println("销毁前：释放资源（如关闭连接池）"); } }5.3 执行顺序初始化流程：调用构造方法创建实例依赖注入（@Autowired、@Resource 等）调用 @PostConstruct 方法（如果实现了 InitializingBean）调用 afterPropertiesSet()（如果配置了 init-method）调用自定义方法销毁流程（容器正常关闭时）：调用 @PreDestroy 方法（如果实现了 DisposableBean）调用 destroy()（如果配置了 destroy-method）调用自定义方法5.4 重要注意事项方法必须 无参数，返回值任意（通常 void）。方法名任意，不要求 init/destroy。prototype 作用域的 Bean，Spring 只调用 @PostConstruct，不会自动调用 @PreDestroy。如果原型 Bean 需要清理，你必须自己设计清理逻辑（例如提供 cleanup() 方法并手动调用）。5.5 “全自动”的条件在 Spring Boot 2.7（Java 8）中，只要满足以下条件，@PostConstruct 和 @PreDestroy 就是 全自动 的：Bean 是 singleton 作用域（默认）。Spring Boot 应用正常关闭（按 Ctrl+C、收到 SIGTERM、程序正常结束）。Spring Boot 会自动注册 JVM 关闭钩子，触发容器关闭，从而调用 @PreDestroy。不要使用 kill -9 强行杀死进程（那样 JVM 直接退出，不会执行关闭钩子）。@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // 按 Ctrl+C 时，会看到 @PreDestroy 的输出 } }六、处理多个同类型 Bean —— 别名、限定符、主次6.1 问题描述在 @Configuration 中定义多个同类型的 @Bean：@Configuration public class AppConfig { @Bean ZoneId createZoneOfZ() { return ZoneId.of("Z"); } @Bean ZoneId createZoneOfUTC8() { return ZoneId.of("UTC+08:00"); } }直接注入 ZoneId 会报错：NoUniqueBeanDefinitionException。6.2 解决方案(1) 为每个 Bean 起一个名字（别名）@Bean("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }(2) 注入时用 @Qualifier 指定名称@Component public class MailService { @Autowired @Qualifier("z") // 精确指定名称为 "z" 的那个 ZoneId private ZoneId zoneId; }或者使用 JSR-250 的 @Resource(name = "z")：@Component public class MailService { @Resource(name = "z") private ZoneId zoneId; }(3) 使用 @Primary 指定主要 Bean当没有使用 @Qualifier 时，Spring 会注入标记 @Primary 的那个 Bean。@Bean @Primary // 主要候选 @Qualifier("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }注入时：@Component public class MailService { @Autowired // 无 @Qualifier，默认注入 @Primary 的那个（即 "z"） private ZoneId zoneId; }6.3 经典应用：多数据源@Configuration public class DataSourceConfig { @Bean @Primary DataSource masterDataSource() { ... } @Bean @Qualifier("slave") DataSource slaveDataSource() { ... } }默认注入主数据源（如 DAO 层无需特殊指定）。需要从库时：@Qualifier("slave") 精确注入。七、FactoryBean —— 工厂模式创建 Bean（了解即可）7.1 是什么FactoryBean 是 Spring 提供的工厂接口，允许你自定义 Bean 的创建逻辑。Spring 会先创建 FactoryBean 实例，再调用其 getObject() 方法返回真正的 Bean。@Component public class ZoneIdFactoryBean implements FactoryBean<ZoneId> { private String zone = "Z"; @Override public ZoneId getObject() throws Exception { return ZoneId.of(zone); } @Override public Class<?> getObjectType() { return ZoneId.class; } }注入/获取时拿到的是 getObject() 返回的 ZoneId。如果需要获取 FactoryBean 本身，可以在名称前加 &：context.getBean("&zoneIdFactoryBean")。7.2 现状在 Spring Boot 2.7 中，@Bean 方法已经足够简洁强大，完全可以替代 FactoryBean 的大部分场景。除非你要编写非常复杂的创建逻辑，或需要与旧框架集成（如 MyBatis 的 MapperFactoryBean），否则推荐直接使用 @Bean 方法。八、综合对比与最佳实践功能推荐方案关键点普通 Bean@Component默认 singleton原型 Bean@Scope("prototype")每次新建，容器不负责 @PreDestroy收集所有实现@Autowired List<Interface>配合 @Order 控制顺序可选依赖@Autowired(required=false)避免异常，可设默认值第三方 Bean@Bean 方法在 @Configuration 中定义初始化逻辑@PostConstruct依赖注入后自动执行销毁逻辑@PreDestroy容器关闭时自动执行（singleton 有效）同类型多个 Bean@Qualifier + 命名精确注入；@Primary 设置默认复杂工厂@Bean 方法一般不用 FactoryBean九、Java 8 + Spring Boot 2.7 特别提醒注解包：@PostConstruct、@PreDestroy、@Resource 使用 javax.annotation，无需添加任何额外依赖。Spring Boot 自动关闭：Spring Boot 应用默认注册 JVM 关闭钩子，只要你正常停止应用（Ctrl+C、kill 不带 -9、System.exit()），@PreDestroy 就会被调用。prototype 的陷阱：如果你的 Bean 是 prototype 且需要释放资源，请考虑改用 singleton，或自行设计清理方法（如实现 DisposableBean 并手动调用）。@Bean 方法的默认名称：方法名即 Bean 名称，注意避免重名（同一类型不同方法名）。