# JavaSE 基础 — Day11 笔记 > **日期**:2026-07-27 > **项目**:`c260727` > **作者**:WanJL --- ## 一、Java 内部类概述 ### 1.1 内部类分类 Java 中的内部类分为三大类: | 类型 | 说明 | |------|------| | **成员内部类** | 定义在类中方法外的类,分为普通成员内部类和静态成员内部类 | | **局部内部类** | 定义在方法体或代码块中的类 | | **匿名内部类** | 没有名字的内部类,定义后直接使用 | > 📄 来源:`Demo01.java` --- ## 二、匿名内部类(核心) ### 2.1 什么是匿名内部类 **匿名内部类**,顾名思义,就是这个类没有名字,是匿名的,一般就是定义之后,直接使用。 ### 2.2 使用前提 存在一个 **类** 或 **接口**,这里的类可以是普通类,也可以是抽象类。 ### 2.3 语法格式 ```java // 基于普通父类/抽象父类 new 类名() { // 重写类的方法 }; // 基于接口 new 接口名() { // 重写接口的方法 }; ``` > 📄 来源:`Demo01.java` ### 2.4 本质 匿名内部类的本质是一个继承了某个类或实现了某个接口的 **【子类的匿名对象】**。 --- ### 2.5 使用方式一:多态接收(变量赋值) 将匿名内部类对象赋值给父类/接口类型的变量,通过多态调用方法。 ```java Usb usb = new Usb() { @Override public void startUsb() { System.out.println("启动鼠标"); } @Override public void closeUsb() { System.out.println("关闭鼠标"); } }; usb.startUsb(); usb.closeUsb(); ``` > 📄 来源:`Demo01.java` ```java Tableware tableware = new Tableware() { @Override public void openTableware() { System.out.println("打开一次性筷子"); } @Override public void useTableware() { System.out.println("使用一次性筷子"); } }; person.eat(tableware); ``` > 📄 来源:`Test02.java` ### 2.6 使用方式二:作为方法参数传递(开发中最常用) 直接创建匿名内部类作为参数传入方法,无需预先定义实现类。 ```java NoteBook nb = new NoteBook(); nb.useUSB(new Usb() { @Override public void startUsb() { System.out.println("使用USB临时设备"); } @Override public void closeUsb() { System.out.println("关闭USB临时设备"); } }); ``` > 📄 来源:`Test01.java` **优势**:如果某个实现类只想在当前场景使用一次,不想被其他人复用,使用匿名内部类可以避免创建独立的实现类文件,使代码更紧凑。 ### 2.7 使用方式三:直接调用方法(匿名对象调用) 在匿名内部类中定义特有的方法,通过匿名对象直接调用(不经过多态)。 ```java new Compute() { @Override public int add(int a, int b) { return a + b; } public void method() { System.out.println("匿名内部类特有的方法"); } }.method(); // 直接调用匿名内部类的特有方法 ``` > 📄 来源:`Demo01.java`(注释部分) **注意**:这种方式因为没有变量名,所以只能调用一次。 ### 2.8 继承普通类的匿名内部类 匿名内部类不仅可以继承抽象类或实现接口,也可以继承 **普通类(非抽象)**。 ```java // Student 是普通类(非抽象、非接口) new Student(1001, "张三", 25, "男") { String s = "哈哈哈哈"; @Override public void study() { System.out.println("临时学员" + getName() + "在学习"); } @Override public void lesson() { System.out.println("临时学员" + getName() + "在上课"); } public void myDay() { study(); lesson(); System.out.println(s); } }.myDay(); // 直接调用匿名内部类的特有方法 ``` > 📄 来源:`exercise01/Test.java` **特点**: - 可以重写父类的非抽象方法(`study()`、`lesson()`) - 可以定义匿名内部类特有的方法(`myDay()`) - 可以通过匿名对象直接调用特有方法 - 适合临时扩展已有类的行为,而不需要显式创建子类 --- ## 三、接口与抽象类定义 ### 3.1 Usb 接口 定义 USB 设备的统一规范。 ```java public interface Usb { void startUsb(); void closeUsb(); } ``` > 📄 来源:`Usb.java` ### 3.2 Tableware 接口 定义餐具的使用规范(开餐、用餐)。 ```java public interface Tableware { void openTableware(); void useTableware(); } ``` > 📄 来源:`Tableware.java` ### 3.3 筷子类(Tableware 接口的实现类) ```java public class 筷子 implements Tableware { @Override public void openTableware() { } @Override public void useTableware() { } } ``` > 📄 来源:`筷子.java` ### 3.4 Compute 抽象类 ```java public abstract class Compute { public abstract int add(int a, int b); } ``` > 📄 来源:`Compute.java` ### 3.5 Sort 接口(学生比较排序) ```java public interface Sort { Student compare(Student s1, Student s2); } ``` > 📄 来源:`Sort.java` --- ## 四、业务类设计 ### 4.1 NoteBook — USB 设备管理器 ```java public class NoteBook { /** * 使用USB设备 * @param usb 接口,实际参数要传入的是实现类对象/匿名内部类对象 */ public void useUSB(Usb usb) { usb.startUsb(); usb.closeUsb(); } } ``` > 📄 来源:`NoteBook.java` ### 4.2 Person — 吃饭场景 ```java public class Person { /** * 吃饭 * @param tableware 吃饭使用的餐具 */ public void eat(Tableware tableware) { tableware.openTableware(); tableware.useTableware(); System.out.println("开始干饭......"); } } ``` > 📄 来源:`Person.java` ### 4.3 Student — 学生实体类 ```java public class Student { private String id; private String name; private Integer age; // 构造方法、getter/setter... } ``` > 📄 来源:`Student.java` --- ## 五、匿名内部类综合案例 ### 5.1 USB 设备接入案例 ```java // 方式一:普通创建(Demo01) Usb usb = new Usb() { @Override public void startUsb() { System.out.println("启动鼠标"); } @Override public void closeUsb() { System.out.println("关闭鼠标"); } }; usb.startUsb(); usb.closeUsb(); // 方式二:作为参数传递(Test01)—— 开发中最常用 NoteBook nb = new NoteBook(); nb.useUSB(new Usb() { @Override public void startUsb() { System.out.println("使用USB临时设备"); } @Override public void closeUsb() { System.out.println("关闭USB临时设备"); } }); ``` ### 5.2 餐具使用案例 ```java Person person = new Person(); Tableware tableware = new Tableware() { @Override public void openTableware() { System.out.println("打开一次性筷子"); } @Override public void useTableware() { System.out.println("使用一次性筷子"); } }; person.eat(tableware); ``` > 📄 来源:`Test02.java` **设计意图**:如果不希望接口实现类被其他人复用,使用匿名内部类可以限制其使用范围。 ### 5.3 学生比较排序案例 ```java Student s1 = new Student("1001", "张三", 20); Student s2 = new Student("1002", "李四", 25); Sort sort = new Sort() { @Override public Student compare(Student s1, Student s2) { if (Integer.getInteger(s1.getId()) < Integer.getInteger(s2.getId())) return s1; else return s2; } }; Student max = sort.compare(s1, s2); System.out.println(max.getName()); ``` > 📄 来源:`Test03.java` **说明**:使用匿名内部类实现 `Sort` 接口的比较逻辑,根据学生 ID 比较大小,返回较小的学生对象。 ### 5.4 抽象类匿名内部类案例 匿名内部类可以直接继承抽象类,并实现其所有抽象方法。 ```java abstract class Animal { public abstract void makeSound(); } public class TestAnonymous { public static void main(String[] args) { new Animal() { @Override public void makeSound() { System.out.println("喵喵喵"); } }.makeSound(); } } ``` > 📄 来源:`exercise07/TestAnonymous.java` ### 5.5 接口多实现案例(计算器) 同一个接口,通过多个匿名内部类实现不同的业务逻辑。 ```java interface Calculator { int calculate(int a, int b); } public class TestCalculator { public static void main(String[] args) { // 加法 new Calculator() { @Override public int calculate(int a, int b) { return a + b; } }.calculate(15, 7); // 减法 new Calculator() { @Override public int calculate(int a, int b) { return a - b; } }.calculate(15, 7); // 乘法 new Calculator() { @Override public int calculate(int a, int b) { return a * b; } }.calculate(15, 7); // 除法 new Calculator() { @Override public int calculate(int a, int b) { return a / b; } }.calculate(15, 7); } } ``` > 📄 来源:`exercise07/TestCalculator.java` **说明**:同一个 `Calculator` 接口,通过 4 个不同的匿名内部类分别实现加、减、乘、除四种运算,体现了匿名内部类灵活多变的特性。 --- ## 六、Java API 概述 ### 6.1 什么是 API **API(Application Programming Interface)**—— 应用程序编程接口。 Java 中的 API 指 JDK 提供的各种功能的 Java 类,这些类把底层的实现封装了起来。我们不需要关心这些类是怎么实现的,只需要学习怎么去使用这些类即可。 我们可以通过 **Java 帮助文档** 来学习怎么使用这些 API。 > 📄 来源:`api/Demo01.java` 包括之前使用过的 `Scanner` 键盘输入类和 `Random` 随机数类,都属于 Java 的 API。 本日讲解的 API 包括: | 分类 | 类/接口 | 说明 | |------|---------|------| | **字符串** | `String` / `StringBuffer` / `StringBuilder` | 不可变字符串与可变字符串 | | **数学** | `Math` | 数学运算工具类 | | **日期时间** | `Date` / `LocalDate` / `LocalTime` / `LocalDateTime` | JDK8 前后日期时间 API | > 📄 来源:`api/Demo01.java`(已更新) ### 6.2 String 字符串类 `String` 是 Java 中最常用的 API 类之一,用于处理字符串数据。 #### 6.2.1 String 的内存模型 String 类型的变量存储位置有两种情况: | 创建方式 | 存储位置 | 特点 | |----------|---------|------| | **直接赋值** `String s = "Hello";` | **字符串常量池** | 只要字符序列(顺序和大小写)相同,JVM 只建立一个 String 对象并维护在常量池中 | | **构造方法创建** `new String("Hello");` | **堆内存** | 每次 `new` 都会在堆中创建一个新对象,内容相同但地址值不同 | ```java String s1 = "Hello"; String s2 = "Hello"; System.out.println(s1 == s2); // true —— 常量池中同一个对象 String s4 = new String("Hello"); String s5 = new String("Hello"); System.out.println(s1 == s4); // false —— 常量池 vs 堆,地址不同 System.out.println(s4 == s5); // false —— 堆中两个不同对象 ``` > 📄 来源:`api/StringTest.java` #### 6.2.2 String 的构造方法 | 构造方法 | 说明 | |----------|------| | `public String()` | 创建空白字符串对象,不含任何内容 | | `public String(char[] chs)` | 根据字符数组的内容创建字符串 | | `public String(String original)` | 根据传入的字符串内容创建字符串 | | `String s = "abc"` | 直接赋值方式创建(常用) | ```java String s1 = new String(); // 空白字符串 char[] chs = {'H', 'e', 'l', 'l', 'o'}; String s2 = new String(chs); // "Hello" String s3 = new String("Hello"); // "Hello" String s4 = "Hello"; // 直接赋值 ``` > 📄 来源:`api/StringTest.java` #### 6.2.3 `==` 与 `equals()` 的区别 | 比较方式 | 基本数据类型 | 引用数据类型 | |----------|-------------|-------------| | **`==`** | 比较具体的值 | 比较内存地址 | | **`equals()`** | — | 比较对象的内容(String 已重写) | ```java String s4 = new String("Hello"); String s5 = new String("Hello"); System.out.println(s4 == s5); // false —— 比较地址 System.out.println(s4.equals(s5)); // true —— 比较内容 ``` > 📄 来源:`api/StringTest.java` ### 6.3 String 常用方法 ```java String str1 = " hello world "; String str2 = " Hello World "; ``` #### 6.3.1 比较方法 | 方法 | 说明 | |------|------| | `equals(String s)` | 比较字符串内容是否相同,**区分大小写** | | `equalsIgnoreCase(String s)` | 比较字符串内容是否相同,**不区分大小写** | ```java boolean b = str1.equals(str2); // false boolean b1 = str1.equalsIgnoreCase(str2); // true(忽略大小写) ``` #### 6.3.2 获取信息方法 | 方法 | 说明 | |------|------| | `length()` | 返回字符串长度 | | `charAt(int index)` | 返回指定索引位置的字符 | | `isEmpty()` | 判断字符串是否为空(长度为 0) | | `isBlank()` | 判断字符串是否为空或空白字符(JDK 11+) | ```java int len = str1.length(); // 15 char c = str1.charAt(2); // ' ' boolean e = str1.isEmpty(); // false boolean b = str1.isBlank(); // false(JDK 11+) ``` #### 6.3.3 截取与查找方法 | 方法 | 说明 | |------|------| | `substring(int begin, int end)` | 截取字符串 `[begin, end)`,含头不含尾 | | `indexOf(String s)` | 返回子字符串第一次出现的位置,未找到返回 -1 | | `contains(String s)` | 判断是否包含指定子字符串 | | `startsWith(String s)` | 判断是否以指定字符串开头 | | `endsWith(String s)` | 判断是否以指定字符串结尾 | ```java String sub = str1.substring(0, 5); // " hel"(索引0~4) int idx = str1.indexOf("lo"); // 2 boolean b = str1.contains("world"); // true ``` #### 6.3.4 转换与替换方法 | 方法 | 说明 | |------|------| | `toUpperCase()` | 字符串转大写 | | `toLowerCase()` | 字符串转小写 | | `trim()` | 去除首尾空格(半角) | | `strip()` | 去除首尾全角/半角空格(JDK 11+) | | `replace(String old, String new)` | 将旧字符串替换为新字符串 | | `split(String regex)` | 按指定字符分割为字符串数组 | | `String.join(String delimiter, CharSequence... elements)` | 使用指定分隔符拼接多个字符串 | ```java String upper = str1.toUpperCase(); // " HELLO WORLD " String lower = str1.toLowerCase(); // " hello world " String trim = str1.trim(); // "hello world" String strip = str1.strip(); // "hello world"(JDK 11+) String replace = str1.replace("world", "java"); // " hello java " String[] arr = "A,B,C".split(","); // ["A", "B", "C"] String joined = String.join("-", "A", "B", "C"); // "A-B-C" ``` > 📄 来源:`api/StringTest02.java` > **注意**:`String` 类比较特殊,**打印其对象名时不会输出内存地址,而是输出该对象所记录的字符串内容**。这个特性是通过重写 `Object` 类的 `toString()` 方法实现的。 #### 6.2.5 String 的不可变性 `String` 是 **不可变类(Immutable Class)**: - `String` 类被 `final` 修饰,不能被继承 - 内部的字符数组/字节数组也被 `final` 修饰 - **每次对 String 的修改都会创建新的对象**,原字符串对象不变 ```java String s = "Hello"; s = s + " World"; // 不是修改原对象,而是创建新对象并重新赋值 ``` > 📄 来源:`api/Demo01.java` **开发建议**: - 字符串拼接使用 `StringBuilder` - 字符串比较使用 `equals()` 而不是 `==` - **频繁改变字符串内容时,不要使用 `String`,而应使用 `StringBuilder`** #### 6.2.6 可变字符串类(StringBuffer / StringBuilder) 为了解决 String 不可变导致的频繁创建对象问题,Java 提供了两个可变字符串类: | 类 | 线程安全性 | 性能 | 使用场景 | |----|-----------|------|---------| | `StringBuffer` | **线程安全**(方法被 `synchronized` 修饰) | 较低 | 多线程环境 | | `StringBuilder` | **线程不安全** | 较高 | 单线程环境(开发中最常用) | **常用方法**:`append()` 追加内容,`toString()` 转换为 String ```java // StringBuffer —— 线程安全,性能较低 StringBuffer sb = new StringBuffer(); sb.append("账号"); sb.append("张三"); sb.append("hello"); sb.append("123456"); String result1 = sb.toString(); System.out.println(result1); // StringBuilder —— 线程不安全,性能较高(推荐) StringBuilder sb1 = new StringBuilder(); sb1.append("账号"); sb1.append("张三"); sb1.append("hello"); sb1.append("123456"); String result2 = sb1.toString(); System.out.println(result2); ``` > 📄 来源:`api/string/StringTest03.java` #### 6.2.7 文本块(Text Block)— JDK 15+ 文本块使用 **三对双引号 `""" ... """`** 定义多行字符串,自动处理缩进和换行,代码更简洁清晰。 ```java // 传统方式:使用 \n 拼接 String s = "姓名:张三\n年龄:20\n性别:男"; System.out.println(s); // 文本块方式(JDK 15+):自动去除尾部空格 + 公共缩进 String s1 = """ 姓名:张三 年龄:20 性别:男 """; System.out.println(s1); ``` > 📄 来源:`api/string/StringTest03.java` ### 6.4 Math 数学类 `Math` 类提供了基本的数学运算方法,所有方法都是 **静态方法**,通过 `类名.方法名()` 直接调用。 ```java System.out.println("π:" + Math.PI); // 3.141592653589793 System.out.println("绝对值:" + Math.abs(-6)); // 6 System.out.println("最大值:" + Math.max(15, 7)); // 15 System.out.println("最小值:" + Math.min(15, 7)); // 7 System.out.println("2的10次方:" + Math.pow(2, 10)); // 1024.0 System.out.println("开根号:" + Math.sqrt(16)); // 4.0 System.out.println("向上取整:" + Math.ceil(3.2)); // 4.0 System.out.println("向下取整:" + Math.floor(3.2)); // 3.0 System.out.println("四舍五入:" + Math.round(3.6)); // 4 System.out.println("[0.0, 1.0)随机数:" + Math.random()); ``` **Math 常用方法一览**: | 方法 | 说明 | 示例 | |------|------|------| | `Math.PI` | 圆周率常量 | `3.141592653589793` | | `abs(int a)` | 绝对值 | `abs(-6)` → `6` | | `max(int a, int b)` | 最大值 | `max(15, 7)` → `15` | | `min(int a, int b)` | 最小值 | `min(15, 7)` → `7` | | `pow(double a, double b)` | 幂运算 aᵇ | `pow(2, 10)` → `1024.0` | | `sqrt(double a)` | 平方根 | `sqrt(16)` → `4.0` | | `ceil(double a)` | 向上取整 | `ceil(3.2)` → `4.0` | | `floor(double a)` | 向下取整 | `floor(3.2)` → `3.0` | | `round(double a)` | 四舍五入 | `round(3.6)` → `4` | | `random()` | 随机数 [0.0, 1.0) | `random()` → `0.xxx` | > 📄 来源:`api/math/MathTest.java` ### 6.5 日期时间类 Java 中处理日期时间有两套 API: #### 6.5.1 JDK8 之前的日期类(了解) | 类 | 说明 | 缺点 | |----|------|------| | `java.util.Date` | 日期时间类 | 设计不佳,大部分方法已废弃 | | `java.text.SimpleDateFormat` | 日期格式化 | **线程不安全** | ```java Date date = new Date(); System.out.println(date); // 输出当前日期时间 ``` > 📄 来源:`api/date/DateTest.java` #### 6.5.2 JDK8 之后的新日期时间 API(推荐) `java.time` 包下的新 API,线程安全、设计优雅。 ##### LocalDate — 日期类(yyyy-MM-dd) ```java // 创建日期 LocalDate today = LocalDate.now(); // 当前日期 LocalDate date = LocalDate.of(2018, 7, 27); // 指定日期 // 获取日期各部分 int year = date.getYear(); // 年份 int monthValue = date.getMonthValue(); // 月份 (1-12) int dayOfMonth = date.getDayOfMonth(); // 当月第几天 int dayOfYear = date.getDayOfYear(); // 当年第几天 DayOfWeek dayOfWeek = date.getDayOfWeek(); // 星期几 // 日期加减(返回新对象,原对象不变) LocalDate date1 = date.plusDays(5); // 加5天 LocalDate date2 = date.plusMonths(6); // 加6月 LocalDate date3 = date.plusYears(5); // 加5年 ``` > 📄 来源:`api/date/LocalDateTest.java` ##### LocalTime — 时间类(HH:mm:ss.nnn) ```java LocalTime time = LocalTime.now(); // 当前时间 LocalTime localTime = LocalTime.of(6, 20, 31); // 指定时间 06:20:31 ``` > 📄 来源:`api/date/LocalDateTest.java` ##### LocalDateTime — 日期+时间类 ```java LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); // 2026-07-27T16:37:02.405 // 使用 DateTimeFormatter 格式化 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"); String formatted = ldt.format(formatter); System.out.println(formatted); // 2026年07月27日 16时37分02秒 ``` > 📄 来源:`api/date/LocalDateTest.java` ##### 新旧日期时间 API 对比 | 对比维度 | JDK8 之前 (`Date`/`Calendar`) | JDK8 之后 (`java.time`) | |----------|-------------------------------|------------------------| | 线程安全 | ❌ 不安全 | ✅ 线程安全(不可变类) | | 设计清晰度 | ❌ 混乱,方法命名不统一 | ✅ 清晰,职责分明 | | 常用类 | `Date`, `SimpleDateFormat` | `LocalDate`, `LocalTime`, `LocalDateTime`, `DateTimeFormatter` | --- ## 七、匿名内部类应用场景总结 | 场景 | 说明 | 示例 | |------|------|------| | 接口实现类仅使用一次 | 避免创建独立的接口实现类文件 | USB设备、一次性餐具 | | 临时逻辑处理 | 实现简单的比较、回调等逻辑 | 学生比较排序 | | 方法参数需要接口/抽象类对象 | 直接传递匿名内部类简化代码 | `nb.useUSB(new Usb(){...})` | **核心优势**: 1. 减少类文件数量,代码更紧凑 2. 适合一次性使用的实现逻辑 3. 可以直接访问外部类的成员(包括私有成员) 4. 在方法参数位置直接创建,代码可读性更强 --- ## 八、随堂练习要点 > 📄 对应练习文件:`课堂练习题/20260727-内部类练习.md` 本次课堂练习涵盖了以下知识点: 1. **成员内部类**:定义在类中,可以直接访问外部类的私有成员 2. **私有内部类**:采用私有内部类设计,对外隐藏实现细节 3. **静态成员内部类**:使用 `static` 修饰,创建格式为 `外部类.内部类 变量 = new 外部类.内部类()` 4. **局部内部类**:定义在方法体中,作用域仅限于当前方法 5. **三种内部类综合运用**:计算机外设管理(USB + 键盘 + 鼠标)、银行账户系统 --- ## 九、拓展阅读 - [Java 匿名内部类(Anonymous Inner Class)详解](https://docs.oracle.com/javase/tutorial/java/javaOO/anonymous.html) - 内部类与 Lambda 表达式的关系(后续课程会详细讲解) - 设计模式中的策略模式(Strategy Pattern)与匿名内部类的结合使用