首页
壁纸
统计
友链
Search
1
Vue2详细笔记
68 阅读
2
ExpressAPI
34 阅读
3
微信小程序
31 阅读
4
Spring MVC 请求生命周期
30 阅读
5
Nestjs概述-中文
29 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
40
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
20
篇与
的结果
2026-07-07
Java接口(Interface)与抽象类(Abstract Class)
这是一份为你定制的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),我可以立刻给你写一份,让你彻底吃透它们的配合使用。😊
2026年07月07日
3 阅读
0 评论
0 点赞
2026-07-07
数组操作
在 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 能让代码更清晰、更优雅。
2026年07月07日
2 阅读
0 评论
1 点赞
2026-06-22
02-Java面向对象编程
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 学习与复习的参考资料。
2026年06月22日
8 阅读
0 评论
1 点赞
2026-06-22
01-Java基础语法
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 输入输出以及包管理机制。
2026年06月22日
4 阅读
0 评论
1 点赞
2026-06-10
Spring MVC 知识地图
🌐 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 整合的实例脑图,可以告诉我。
2026年06月10日
6 阅读
0 评论
1 点赞
1
2
...
4