前言
前面三篇博客我们依次讲解了集合整体架构与 Collection 父接口、单列集合三种通用遍历 + 迭代器底层、JDK8 方法引用。本篇为第四部分收尾,一次性讲完剩余全部核心内容:List 接口特性与并发修改异常、四大基础数据结构、ArrayList 与 LinkedList 底层原理、完整泛型体系(泛型类 / 泛型方法 / 泛型接口 / 通配符)。
一、List 接口核心知识点
1. List 集合三大特点
- 存取有序:存入和取出元素顺序完全一致
- 拥有数字索引,可以通过索引精准操作元素
- 允许存储重复元素常用实现类:
ArrayList、LinkedList
2. List 独有的索引操作 API
| 方法 | 功能说明 |
|---|---|
void add(int index,E element) |
在指定索引位置插入元素 |
E remove(int index) |
删除指定索引元素,返回被删除元素 |
E set(int index,E element) |
修改指定索引元素,返回旧元素 |
E get(int index) |
根据索引获取对应元素 |
代码演示:
import java.util.ArrayList;
import java.util.List;
public class ListApiTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add(1, "插入元素");
System.out.println(list);
System.out.println("索引1元素:" + list.get(1));
String oldVal = list.set(1, "替换");
System.out.println("被替换内容:" + oldVal);
String delVal = list.remove(0);
System.out.println("被删除内容:" + delVal);
}
}
3. List 五种遍历方式
- 通用三种(所有单列集合通用):迭代器 Iterator、增强 for 循环、forEach () 方法
- List 独有两种:普通 for 循环(依靠索引)、ListIterator 列表迭代器
4. 并发修改异常 ConcurrentModificationException
异常产生原因
使用普通 Iterator 迭代器遍历集合时,直接调用集合自身add()/remove()修改元素,会抛出并发修改异常。
// 报错示例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
if("1".equals(s)){
list.add("3"); // 直接调用集合add,触发异常
}
}
解决方案
- 遍历中删除元素:使用迭代器自带
remove()方法 - 遍历中添加元素:使用
ListIterator列表迭代器的add()方法
二、四大基础数据结构
数据结构是数据在内存中存储、组织的方式,直接决定集合增删查询效率。
1. 栈
规则:后进先出、先进后出操作:压栈(存入)、弹栈(取出),仅栈顶可操作。
2. 队列
规则:先进先出、后进后出操作:队尾入队,队头出队。
3. 数组
内存空间连续开辟优点:根据索引查询速度极快,随机访问效率高缺点:中间位置增删元素,需要批量移动大量数据,效率低
4. 链表
由多个独立结点组成,内存不连续;每个结点包含:数据、下一个结点地址(双向链表额外存储前驱结点地址)优点:首尾增删元素无需移动其他数据,效率高缺点:查询元素必须从头结点依次遍历,随机查询速度慢
小结:栈:后进先出;队列:先进先出;数组:查询快、中间增删慢;链表:查询慢、首尾增删快。
三、ArrayList 与 LinkedList 底层原理
1. ArrayList
- 底层数据结构:数组
- 性能特点:查询速度快,中间位置增删效率低
- 扩容规则:
- 空参构造创建时,底层初始为空数组
- 添加第一个元素,初始化长度 10 的数组
- 数组存满后,自动扩容为原长度 1.5 倍
- 扩容逻辑:创建更长新数组,拷贝原数组所有元素,再存入新元素。
2. LinkedList
-
底层数据结构:双向链表
-
性能特点:随机索引查询慢,首尾增删元素效率极高
-
特有首尾操作方法
public void addFirst(E e) // 头部添加元素
public void addLast(E e) // 尾部添加元素
public E getFirst() // 获取第一个元素
public E getLast() // 获取最后一个元素
public E removeFirst() // 删除并返回首元素
public E removeLast() // 删除并返回尾元素 -
注意:
get(index)看似按索引取值,底层会从头 / 尾双向遍历查找,不适合大量随机索引查询。
四、泛型完整体系
1. 泛型基础介绍
- 出现版本:JDK5
- 作用:编译阶段约束集合存储的数据类型,提前做类型校验
- 核心好处:统一集合内数据类型;将运行时类型转换异常提前到编译期报错
- 注意:泛型只能写引用数据类型,不能使用基本数据类型
对比代码(无泛型 vs 带泛型)
// 无泛型,可存入任意类型,类型混乱
ArrayList list = new ArrayList();
list.add(666);
list.add("aaa");
list.add(66.66);
// 指定泛型String,只能存字符串,编译校验
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add(666); // 编译直接报错
2. 泛型类
定义格式:类名<泛型标识>示例:public class ArrayList<E>使用时创建对象,确定泛型具体类型:
// 确定泛型为String
ArrayList<String> list = new ArrayList<>();
// 确定泛型为Integer
ArrayList<Integer> list2 = new ArrayList<>();
3. 泛型方法
分为两类:
-
非静态泛型方法:复用类上定义的泛型
-
静态泛型方法:需要单独声明自身泛型标识
// 静态泛型方法,独立声明
public staticvoid printArray(T[] array){
for(T t : array){
System.out.println(t);
}
}
4. 泛型接口
定义格式:public interface List<E>类实现泛型接口两种方式:
-
实现类直接固定泛型类型
-
实现类保留泛型,创建对象时再确定类型
// 方式1:直接固定String
public class Demo implements List{} // 方式2:保留泛型,实例化时指定
public class Demoimplements List {}
5. 泛型通配符
| 通配符写法 | 含义 |
|---|---|
? |
任意类型 |
? extends E |
只能接收 E 类型或 E 的子类 |
? super E |
只能接收 E 类型或 E 的父类 |
五、本篇完整小结
- List 三大特性:有序、有索引、可存重复;提供 4 个索引专属 API,共 5 种遍历方式;
- 迭代遍历直接调用集合 add/remove 会触发并发修改异常,需使用迭代器自带方法;
- 栈、队列、数组、链表四大基础数据结构,牢记各自存取性能特点;
- ArrayList 底层数组,查询快,扩容 1.5 倍;LinkedList 底层双向链表,首尾增删高效;
- 泛型 JDK5 推出,约束集合数据类型,提前校验报错;
- 泛型分为泛型类、泛型方法、泛型接口、泛型通配符四大模块,对应不同使用场景。