欢迎大家来到我们后端的第三次授课,今天我们主要讲的是接口,集合,泛型,异常处理
接口
接口中的变量为什么是 public static final?
接口中定义的变量其实就是 常量。
写成下面这样:
1 | int AGE = 10; |
自动等价于:
1 | public static final int AGE = 10; |
原因:
- static:接口是“规范”,变量属于整个接口类,而不是对象
- final:常量必须固定值
- public:常量是公开给外界使用的
试图写成 private 或 protected 会报错,因为接口变量必须是公开的常量。
abstract void run();
只有声明没有实现,继承其的方法必须override这个方法
意思就是:
- 这是一个公共的方法
- 没有实现
- 强制子类必须实现它
👉 这是最常见的“规范定义方式”,用于框架、API、接口、抽象类
1 | Test test = new Test() { |
意思是:
new Test() { ... }并不是“实例化接口”- 而是 创建一个匿名类,这个匿名类实现了 Test 接口
- 然后再 new 这个匿名类的对象并赋给
test
所以它是合法的。
接口不能直接 new,但可以通过匿名内部类在 new 后面立刻写出它的实现。
匿名类是一个没有名字的类,用于在 new 的时候立即实现接口或父类,用于一次性场景(回调、线程、事件监听)。
你可以把匿名类理解为:
“我临时找个人干这件事,不用给他身份证,干完这件事就结束。”
而正式写一个类:
“我建档案、取名字,这个人以后还能继续用。”
最后说
在实际开发中,我们经常通过抽象类或接口来降低耦合,上层只依赖接口,而具体实现放在不同的实现类里。这样做的好处是明显的:代码更容易维护,也更容易扩展。以后如果有新的需求,只需要写新的实现类,而不用动上层业务逻辑,系统就可以灵活适配变化。
在Java中,类的多继承是不合法,但接口允许多继承。
public interface Hockey extends Sports, Event
抽象方法
| 特点 | 说明 |
|---|---|
| 1. 没有方法体 | 只有方法名、参数、返回类型 |
| 2. 必须定义在抽象类或接口中 | 普通类不能包含抽象方法 |
| 3. 子类必须重写(实现)它 | 否则子类也必须是抽象类 |
| 4. 作用是定义“规范” | 让不同子类都遵守相同的命名和行为接口 |
统一规范、实现多态。
集合
在 Java 的集合框架中,接口(Interface) 定义了“功能规范”,
而 实现类(Implementation Class) 才是真正“干活”的对象。
换句话说:前面写的类型是接口,后面 new 的是实现类。
举个例子说明:
1 | java |
List是一个接口(只是定义了方法,比如add()、get()、remove()等)。ArrayList是一个实现类(真正实现了这些方法,比如用数组来存储数据)。
你也可以写成:
1 | java |
这时:
- 代码调用方式不变(仍然是
list.add()、list.get())。 - 但底层实现方式变了(
LinkedList用链表存储,而不是数组)。
接口类型 = 依赖规范,增强灵活性和可扩展性;具体实现类 = 真正存数据和提供功能。
这里大部分的我们不会太常用,需要用到的时候再查也是可以的
| 方法 | 功能 | 例子 |
|---|---|---|
Collections.sort(List<T>) |
排序 | 对 ArrayList 或 LinkedList 排序 |
Collections.shuffle(List<T>) |
打乱顺序 | 洗牌操作 |
Collections.reverse(List<T>) |
反转 | List 元素反序 |
Collections.binarySearch(List<T>, key) |
二分查找 | 查找元素索引 |
Collections.max(List<T>) |
最大值 | 找出最大元素 |
Collections.min(List<T>) |
最小值 | 找出最小元素 |
普通的
Iterator只能 单向 遍历;而
ListIterator可以 双向遍历(前进、后退),还能 修改列表内容。Iterator不属于某个具体集合类,而是集合框架提供的统一遍历工具
一、什么是迭代器(Iterator)
/ɪtə’retɚ/
迭代器(Iterator) 是 Java 提供的一种 遍历集合的统一方式。
📘 它是一个接口,定义在 java.util 包中,常用于遍历 Collection 类型的集合(如 ArrayList、HashSet、LinkedList 等)。
不管集合底层是数组、链表还是哈希表,迭代器都能用相同的方式来访问其中的元素。
🧠 二、Iterator 接口的核心方法
1 | java |
| 方法 | 作用 | 说明 |
|---|---|---|
hasNext() |
判断集合中是否还有元素 | 如果有则返回 true |
next() |
返回集合中的下一个元素 | 如果没有元素,会抛出 NoSuchElementException |
remove() |
删除上一次 next() 返回的元素 |
只能在调用 next() 之后使用一次 |
1 | import java.util.*; |
**遍历集合时,不要用集合自身的 remove() 删除元素,要用 Iterator 提供的 remove() 方法,否则会抛 ConcurrentModificationException。**并发修改异常
- 集合(ArrayList)内部有一个计数器
modCount,用来记录结构修改次数 - Iterator 每次调用
next()会检查modCount是否变化 - 如果你直接用
list.remove()删除元素 →modCount变化了 → Iterator 检测到 → 报错 iterator.remove()是 Iterator 提供的官方方法,会同步更新计数器,安全删除
五、ListIterator:双向迭代器
ListIterator 是 Iterator 的子接口,只用于 List 集合,
比 Iterator 功能更强大,支持:
- 双向遍历;
- 添加元素;
- 修改当前元素。
1 | List<String> list = new ArrayList<>(); |
ListIterator
ListIterator
✅ 注意:只有 next() 会改变游标位置。
✅ 只有 previous() 会改变游标位置。
泛型
🍉(1)成员变量
code:状态码,如 200 成功,404 未找到message:提示信息,如 “success”、”error”data:真正要返回的数据,因为用了T,数据类型是可变的
1 | public GlobalResponse(int code, String message, T data) { |
🍇(2)构造方法
用于创建对象时给字段赋值,例如:
1 | GlobalResponse<String> resp = new GlobalResponse<>(200, "成功", "登录成功"); |
T 就变成了 String。
1 | public void show() { |
🍎(3)show() 方法
把响应内容按格式打印出来。
例如调用:
1 | resp.show(); |
输出:
1 | 状态码:200,消息:成功,数据:登录成功 |
⚠ 关键问题:左边没写泛型(变成 Raw Type 原始类型)
GlobalResponse response1 —— 没写 <String>。
虽然右边用了 <>,但编译器无法推断应该是什么类型,因为 左边没给类型信息。
因此,左边这一写法导致 整个对象变成“原始类型(raw type)”。
❌ 原始类型的危险在哪里?
① 泛型检查失效
编译器无法检查 data 的类型,你可以随便放任何类型进去:
1 | java |
这会导致运行时崩溃,比如:
1 | java |
② 编译器会给黄色警告(不安全)
你会看到:
1 | pgsql |
意思是:
你没有用泛型,这是不安全的,建议补上类型。
| 写法 | 可以传入的类型 | 主要用途 |
|---|---|---|
? extends Number |
Number 的子类 | 安全读取(Producer) |
? super Number |
Number 的父类 | 安全写入(Consumer) |
super 限制的是“这个泛型的类型参数必须是 Number 或其父类(不接受子类)”,但写入内容时可以写入 Number 及其子类。
1. FileReader 构造方法可能抛出 受检异常(Checked Exception)
在 Java 中,异常分为两类:
| 类型 | 代表类 | 是否必须处理 | 示例 |
|---|---|---|---|
| 受检异常(Checked Exception) | IOException, SQLException |
✅ 必须处理或声明 | 文件读写、数据库连接等 |
| 非受检异常(Unchecked Exception) | NullPointerException, ArithmeticException |
❌ 可不处理 | 空指针、除零错误等 |
1 | class MyException extends Exception { |
getMessage() 方法来自父类 Throwable,它会返回你在构造时通过 super(message) 传进去的那段文字。
异常处理
当程序出现异常时,printStackTrace() 会把异常的:
- 类型(Exception 类型名)
- 出错原因(message)
- 出错位置(哪一行代码抛出的)
- 方法调用路径(调用栈 stack trace)
打印到控制台(标准错误输出)
异常如果不处理,会一路向上传递,最终导致程序中断或崩溃。Web 项目中未处理异常最终会导致接口报 500。所以必须做异常处理,或统一异常捕获。
┌────────── Exception(异常,可处理)
Throwable
└────────── Error(严重错误,不建议处理)
多异常合并捕获
从 Java 7 开始,引入了一个更简洁的写法 —— 多异常合并捕获,可以用一个 catch 块处理多个无继承关系的异常。
1 | try { |
需要注意:
- 异常类型1、异常类型2 等 不能有继承关系,否则会导致编译错误。
- 异常变量名 是这三种异常的共同引用变量,因此在 catch 块内你不能调用它们特有的方法。
- 编译器会推断出这个异常变量的类型为这几个异常的最近公共父类(比如 Exception 或 IOException)。
如下这种互不继承的异常,同时可能抛出 SQLException 和 IOException:
实例
try {
// 同时可能抛出 SQL 和 IO 异常
doSomething();
} catch (SQLException | IOException e) {
System.out.println(“发生了 SQL 或 IO 异常!”);
e.printStackTrace();
}
错误示例(不能合并父子类异常):
1 | // 错误写法! |
FileNotFoundException 是 IOException 的子类,所以这个写法实际上是不合法的,会报编译错误。
一、super(message) 的含义
super() 是 Java 中用于调用 父类构造方法 的关键字。
而这里的
1 | java |
表示在创建 MyException 对象时,把 message 传递给父类 Exception 的构造方法。
Exception 类本身在 JDK 源码中是这样的:
1 | java |
而它的父类 Throwable 里定义了一个字段:
1 | java |
用于保存异常信息。
也就是说,super(message) 这句话的作用是:
✅ 把
"自定义异常被触发!"这段文本保存到异常对象内部,用于后续输出或日志记录。W