欢迎大家来到我们后端的第三次授课,今天我们主要讲的是接口,集合,泛型,异常处理

接口

接口中的变量为什么是 public static final

接口中定义的变量其实就是 常量

写成下面这样:

1
int AGE = 10;

自动等价于:

1
public static final int AGE = 10;

原因:

  • static:接口是“规范”,变量属于整个接口类,而不是对象
  • final:常量必须固定值
  • public:常量是公开给外界使用的

试图写成 privateprotected 会报错,因为接口变量必须是公开的常量。

abstract void run();

只有声明没有实现,继承其的方法必须override这个方法

意思就是:

  • 这是一个公共的方法
  • 没有实现
  • 强制子类必须实现它

👉 这是最常见的“规范定义方式”,用于框架、API、接口、抽象类

1
2
3
4
5
6
Test test = new Test() {
@Override
public void start() {

}
};

意思是:

  • 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
2
java	
List<String> list = new ArrayList<>();
  • List 是一个接口(只是定义了方法,比如 add()get()remove() 等)。
  • ArrayList 是一个实现类(真正实现了这些方法,比如用数组来存储数据)。

你也可以写成:

1
2
java
List<String> list = new LinkedList<>();

这时:

  • 代码调用方式不变(仍然是 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 类型的集合(如 ArrayListHashSetLinkedList 等)。

不管集合底层是数组、链表还是哈希表,迭代器都能用相同的方式来访问其中的元素


🧠 二、Iterator 接口的核心方法

1
2
3
4
5
6
7
java

public interface Iterator<E> {
boolean hasNext(); // 判断是否还有下一个元素
E next(); // 返回下一个元素
void remove(); // 删除最近返回的那个元素(可选)
}
方法 作用 说明
hasNext() 判断集合中是否还有元素 如果有则返回 true
next() 返回集合中的下一个元素 如果没有元素,会抛出 NoSuchElementException
remove() 删除上一次 next() 返回的元素 只能在调用 next() 之后使用一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.*;

public class IteratorDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

// 获取迭代器
Iterator<String> iterator = list.iterator();

// 使用迭代器遍历
while (iterator.hasNext()) {
String lang = iterator.next();
System.out.println(lang);

// 删除元素的正确方式(不能用 list.remove())
if (lang.equals("Python")) {
iterator.remove();
}
}

System.out.println("删除后: " + list);
}
}

**遍历集合时,不要用集合自身的 remove() 删除元素,要用 Iterator 提供的 remove() 方法,否则会抛 ConcurrentModificationException。**并发修改异常

  • 集合(ArrayList)内部有一个计数器 modCount,用来记录结构修改次数
  • Iterator 每次调用 next() 会检查 modCount 是否变化
  • 如果你直接用 list.remove() 删除元素 → modCount 变化了 → Iterator 检测到 → 报错
  • iterator.remove() 是 Iterator 提供的官方方法,会同步更新计数器,安全删除

五、ListIterator:双向迭代器

ListIteratorIterator 的子接口,只用于 List 集合
Iterator 功能更强大,支持:

  • 双向遍历;
  • 添加元素;
  • 修改当前元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");

ListIterator<String> it = list.listIterator();

// 正向遍历
while (it.hasNext()) {
System.out.println(it.next());
}

// 反向遍历
System.out.println("反向:");
while (it.hasPrevious()) {
System.out.println(it.previous());
}

ListIterator iterator = list.listIterator(0); // 游标复位到开头
ListIterator itEnd = list.listIterator(list.size()); // 游标移动到末尾

注意:只有 next() 会改变游标位置。

✅ 只有 previous() 会改变游标位置。

泛型

🍉(1)成员变量

  • code:状态码,如 200 成功,404 未找到
  • message:提示信息,如 “success”、”error”
  • data:真正要返回的数据,因为用了 T,数据类型是可变的

1
2
3
4
5
public GlobalResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

🍇(2)构造方法

用于创建对象时给字段赋值,例如:

1
GlobalResponse<String> resp = new GlobalResponse<>(200, "成功", "登录成功");

T 就变成了 String


1
2
3
4
    public void show() {
System.out.println("状态码:" + code + ",消息:" + message + ",数据:" + data);
}
}

🍎(3)show() 方法

把响应内容按格式打印出来。

例如调用:

1
resp.show();

输出:

1
状态码:200,消息:成功,数据:登录成功

⚠ 关键问题:左边没写泛型(变成 Raw Type 原始类型)

GlobalResponse response1 —— 没写 <String>

虽然右边用了 <>,但编译器无法推断应该是什么类型,因为 左边没给类型信息

因此,左边这一写法导致 整个对象变成“原始类型(raw type)”


❌ 原始类型的危险在哪里?

① 泛型检查失效

编译器无法检查 data 的类型,你可以随便放任何类型进去:

1
2
3
4
5
6
java


复制代码
GlobalResponse response1 = new GlobalResponse<>(200, "OK", "操作成功");
response1 = new GlobalResponse<>(200, "OK", 123); // 编译也不报错

这会导致运行时崩溃,比如:

1
2
3
4
5
java


复制代码
String s = (String) response1.getData(); // 可能 ClassCastException

② 编译器会给黄色警告(不安全)

你会看到:

1
2
3
4
5
pgsql


复制代码
GlobalResponse is a raw type. References to generic type GlobalResponse<T> should be parameterized

意思是:

你没有用泛型,这是不安全的,建议补上类型。

写法 可以传入的类型 主要用途
? 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyException extends Exception {
public MyException(String message) {
super(message);
}
}

public class Exception05 {
public static void main(String[] args) {
try {
throw new MyException("自定义异常被触发!");
} catch (MyException e) {
System.out.println("捕获到:" + e.getMessage());
}
}
}

getMessage() 方法来自父类 Throwable,它会返回你在构造时通过 super(message) 传进去的那段文字。

异常处理

当程序出现异常时,printStackTrace() 会把异常的:

  • 类型(Exception 类型名)
  • 出错原因(message)
  • 出错位置(哪一行代码抛出的)
  • 方法调用路径(调用栈 stack trace)

打印到控制台(标准错误输出)

异常如果不处理,会一路向上传递,最终导致程序中断或崩溃。Web 项目中未处理异常最终会导致接口报 500。所以必须做异常处理,或统一异常捕获。

          ┌────────── Exception(异常,可处理)

Throwable
└────────── Error(严重错误,不建议处理)

多异常合并捕获

从 Java 7 开始,引入了一个更简洁的写法 —— 多异常合并捕获,可以用一个 catch 块处理多个无继承关系的异常。

1
2
3
4
5
try {
// 可能抛出多个不同类型异常的代码
} catch (异常类型1 | 异常类型2 | 异常类型3 异常变量) {
// 统一处理
}

需要注意:

  • 异常类型1、异常类型2 等 不能有继承关系,否则会导致编译错误。
  • 异常变量名 是这三种异常的共同引用变量,因此在 catch 块内你不能调用它们特有的方法。
  • 编译器会推断出这个异常变量的类型为这几个异常的最近公共父类(比如 Exception 或 IOException)。

如下这种互不继承的异常,同时可能抛出 SQLException 和 IOException:

实例

try {
// 同时可能抛出 SQL 和 IO 异常
doSomething();
} catch (SQLException | IOException e) {
System.out.println(“发生了 SQL 或 IO 异常!”);
e.printStackTrace();
}

错误示例(不能合并父子类异常):

1
2
3
4
// 错误写法!
catch (FileNotFoundException | IOException e) {
// 会导致编译错误,因为 FileNotFoundException 是 IOException 的子类
}

FileNotFoundException 是 IOException 的子类,所以这个写法实际上是不合法的,会报编译错误。

一、super(message) 的含义

super() 是 Java 中用于调用 父类构造方法 的关键字。

而这里的

1
2
3
4
5
java


复制代码
super(message);

表示在创建 MyException 对象时,把 message 传递给父类 Exception 的构造方法

Exception 类本身在 JDK 源码中是这样的:

1
2
3
4
5
6
7
java


复制代码
public Exception(String message) {
super(message);
}

而它的父类 Throwable 里定义了一个字段:

1
2
3
4
5
java


复制代码
private String detailMessage;

用于保存异常信息。

也就是说,super(message) 这句话的作用是:

✅ 把 "自定义异常被触发!" 这段文本保存到异常对象内部,用于后续输出或日志记录。W

更新于

请我喝[茶]~( ̄▽ ̄)~*

BaiYaoShi 微信支付

微信支付

BaiYaoShi 支付宝

支付宝

BaiYaoShi 贝宝

贝宝