20260727-笔记.md 23 KB

JavaSE 基础 — Day11 笔记

日期:2026-07-27
项目c260727
作者:WanJL


一、Java 内部类概述

1.1 内部类分类

Java 中的内部类分为三大类:

类型 说明
成员内部类 定义在类中方法外的类,分为普通成员内部类和静态成员内部类
局部内部类 定义在方法体或代码块中的类
匿名内部类 没有名字的内部类,定义后直接使用

📄 来源:Demo01.java


二、匿名内部类(核心)

2.1 什么是匿名内部类

匿名内部类,顾名思义,就是这个类没有名字,是匿名的,一般就是定义之后,直接使用。

2.2 使用前提

存在一个 接口,这里的类可以是普通类,也可以是抽象类。

2.3 语法格式

// 基于普通父类/抽象父类
new 类名() {
    // 重写类的方法
};

// 基于接口
new 接口名() {
    // 重写接口的方法
};

📄 来源:Demo01.java

2.4 本质

匿名内部类的本质是一个继承了某个类或实现了某个接口的 【子类的匿名对象】


2.5 使用方式一:多态接收(变量赋值)

将匿名内部类对象赋值给父类/接口类型的变量,通过多态调用方法。

Usb usb = new Usb() {
    @Override
    public void startUsb() {
        System.out.println("启动鼠标");
    }

    @Override
    public void closeUsb() {
        System.out.println("关闭鼠标");
    }
};
usb.startUsb();
usb.closeUsb();

📄 来源:Demo01.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 使用方式二:作为方法参数传递(开发中最常用)

直接创建匿名内部类作为参数传入方法,无需预先定义实现类。

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 使用方式三:直接调用方法(匿名对象调用)

在匿名内部类中定义特有的方法,通过匿名对象直接调用(不经过多态)。

new Compute() {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    public void method() {
        System.out.println("匿名内部类特有的方法");
    }
}.method();  // 直接调用匿名内部类的特有方法

📄 来源:Demo01.java(注释部分)

注意:这种方式因为没有变量名,所以只能调用一次。

2.8 继承普通类的匿名内部类

匿名内部类不仅可以继承抽象类或实现接口,也可以继承 普通类(非抽象)

// 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 设备的统一规范。

public interface Usb {
    void startUsb();
    void closeUsb();
}

📄 来源:Usb.java

3.2 Tableware 接口

定义餐具的使用规范(开餐、用餐)。

public interface Tableware {
    void openTableware();
    void useTableware();
}

📄 来源:Tableware.java

3.3 筷子类(Tableware 接口的实现类)

public class 筷子 implements Tableware {
    @Override
    public void openTableware() { }

    @Override
    public void useTableware() { }
}

📄 来源:筷子.java

3.4 Compute 抽象类

public abstract class Compute {
    public abstract int add(int a, int b);
}

📄 来源:Compute.java

3.5 Sort 接口(学生比较排序)

public interface Sort {
    Student compare(Student s1, Student s2);
}

📄 来源:Sort.java


四、业务类设计

4.1 NoteBook — USB 设备管理器

public class NoteBook {
    /**
     * 使用USB设备
     * @param usb 接口,实际参数要传入的是实现类对象/匿名内部类对象
     */
    public void useUSB(Usb usb) {
        usb.startUsb();
        usb.closeUsb();
    }
}

📄 来源:NoteBook.java

4.2 Person — 吃饭场景

public class Person {
    /**
     * 吃饭
     * @param tableware 吃饭使用的餐具
     */
    public void eat(Tableware tableware) {
        tableware.openTableware();
        tableware.useTableware();
        System.out.println("开始干饭......");
    }
}

📄 来源:Person.java

4.3 Student — 学生实体类

public class Student {
    private String id;
    private String name;
    private Integer age;

    // 构造方法、getter/setter...
}

📄 来源:Student.java


五、匿名内部类综合案例

5.1 USB 设备接入案例

// 方式一:普通创建(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 餐具使用案例

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 学生比较排序案例

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 抽象类匿名内部类案例

匿名内部类可以直接继承抽象类,并实现其所有抽象方法。

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 接口多实现案例(计算器)

同一个接口,通过多个匿名内部类实现不同的业务逻辑。

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 都会在堆中创建一个新对象,内容相同但地址值不同
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" 直接赋值方式创建(常用)
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 已重写)
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 常用方法

String str1 = "  hello world  ";
String str2 = "  Hello World  ";

6.3.1 比较方法

方法 说明
equals(String s) 比较字符串内容是否相同,区分大小写
equalsIgnoreCase(String s) 比较字符串内容是否相同,不区分大小写
boolean b  = str1.equals(str2);          // false
boolean b1 = str1.equalsIgnoreCase(str2); // true(忽略大小写)

6.3.2 获取信息方法

方法 说明
length() 返回字符串长度
charAt(int index) 返回指定索引位置的字符
isEmpty() 判断字符串是否为空(长度为 0)
isBlank() 判断字符串是否为空或空白字符(JDK 11+)
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) 判断是否以指定字符串结尾
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) 使用指定分隔符拼接多个字符串
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 的修改都会创建新的对象,原字符串对象不变

    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

// 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+

文本块使用 三对双引号 """ ... """ 定义多行字符串,自动处理缩进和换行,代码更简洁清晰。

// 传统方式:使用 \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 类提供了基本的数学运算方法,所有方法都是 静态方法,通过 类名.方法名() 直接调用。

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 日期格式化 线程不安全
Date date = new Date();
System.out.println(date);  // 输出当前日期时间

📄 来源:api/date/DateTest.java

6.5.2 JDK8 之后的新日期时间 API(推荐)

java.time 包下的新 API,线程安全、设计优雅。

LocalDate — 日期类(yyyy-MM-dd)
// 创建日期
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)
LocalTime time     = LocalTime.now();          // 当前时间
LocalTime localTime = LocalTime.of(6, 20, 31); // 指定时间 06:20:31

📄 来源:api/date/LocalDateTest.java

LocalDateTime — 日期+时间类
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 + 键盘 + 鼠标)、银行账户系统

九、拓展阅读