Java 集合高级(四)List 接口、底层数据结构、ArrayList&LinkedList、泛型全套详解

前言

前面三篇博客我们依次讲解了集合整体架构与 Collection 父接口、单列集合三种通用遍历 + 迭代器底层、JDK8 方法引用。本篇为第四部分收尾,一次性讲完剩余全部核心内容:List 接口特性与并发修改异常、四大基础数据结构、ArrayList 与 LinkedList 底层原理、完整泛型体系(泛型类 / 泛型方法 / 泛型接口 / 通配符)。

一、List 接口核心知识点

1. List 集合三大特点

  1. 存取有序:存入和取出元素顺序完全一致
  2. 拥有数字索引,可以通过索引精准操作元素
  3. 允许存储重复元素常用实现类:ArrayListLinkedList

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 五种遍历方式

  1. 通用三种(所有单列集合通用):迭代器 Iterator、增强 for 循环、forEach () 方法
  2. 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,触发异常
    }
}
解决方案
  1. 遍历中删除元素:使用迭代器自带remove()方法
  2. 遍历中添加元素:使用ListIterator列表迭代器的add()方法

二、四大基础数据结构

数据结构是数据在内存中存储、组织的方式,直接决定集合增删查询效率。

1. 栈

规则:后进先出、先进后出操作:压栈(存入)、弹栈(取出),仅栈顶可操作。

2. 队列

规则:先进先出、后进后出操作:队尾入队,队头出队。

3. 数组

内存空间连续开辟优点:根据索引查询速度极快,随机访问效率高缺点:中间位置增删元素,需要批量移动大量数据,效率低

4. 链表

由多个独立结点组成,内存不连续;每个结点包含:数据、下一个结点地址(双向链表额外存储前驱结点地址)优点:首尾增删元素无需移动其他数据,效率高缺点:查询元素必须从头结点依次遍历,随机查询速度慢

小结:栈:后进先出;队列:先进先出;数组:查询快、中间增删慢;链表:查询慢、首尾增删快。

三、ArrayList 与 LinkedList 底层原理

1. ArrayList

  1. 底层数据结构:数组
  2. 性能特点:查询速度快,中间位置增删效率低
  3. 扩容规则:
    • 空参构造创建时,底层初始为空数组
    • 添加第一个元素,初始化长度 10 的数组
    • 数组存满后,自动扩容为原长度 1.5 倍
  4. 扩容逻辑:创建更长新数组,拷贝原数组所有元素,再存入新元素。

2. LinkedList

  1. 底层数据结构:双向链表

  2. 性能特点:随机索引查询慢,首尾增删元素效率极高

  3. 特有首尾操作方法

    public void addFirst(E e) // 头部添加元素
    public void addLast(E e) // 尾部添加元素
    public E getFirst() // 获取第一个元素
    public E getLast() // 获取最后一个元素
    public E removeFirst() // 删除并返回首元素
    public E removeLast() // 删除并返回尾元素

  4. 注意:get(index)看似按索引取值,底层会从头 / 尾双向遍历查找,不适合大量随机索引查询。

四、泛型完整体系

1. 泛型基础介绍

  1. 出现版本:JDK5
  2. 作用:编译阶段约束集合存储的数据类型,提前做类型校验
  3. 核心好处:统一集合内数据类型;将运行时类型转换异常提前到编译期报错
  4. 注意:泛型只能写引用数据类型,不能使用基本数据类型

对比代码(无泛型 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. 泛型方法

分为两类:

  1. 非静态泛型方法:复用类上定义的泛型

  2. 静态泛型方法:需要单独声明自身泛型标识

    // 静态泛型方法,独立声明
    public static void printArray(T[] array){
    for(T t : array){
    System.out.println(t);
    }
    }

4. 泛型接口

定义格式:public interface List<E>类实现泛型接口两种方式:

  1. 实现类直接固定泛型类型

  2. 实现类保留泛型,创建对象时再确定类型

    // 方式1:直接固定String
    public class Demo implements List{}

    // 方式2:保留泛型,实例化时指定
    public class Demo implements List{}

5. 泛型通配符

通配符写法 含义
? 任意类型
? extends E 只能接收 E 类型或 E 的子类
? super E 只能接收 E 类型或 E 的父类

五、本篇完整小结

  1. List 三大特性:有序、有索引、可存重复;提供 4 个索引专属 API,共 5 种遍历方式;
  2. 迭代遍历直接调用集合 add/remove 会触发并发修改异常,需使用迭代器自带方法;
  3. 栈、队列、数组、链表四大基础数据结构,牢记各自存取性能特点;
  4. ArrayList 底层数组,查询快,扩容 1.5 倍;LinkedList 底层双向链表,首尾增删高效;
  5. 泛型 JDK5 推出,约束集合数据类型,提前校验报错;
  6. 泛型分为泛型类、泛型方法、泛型接口、泛型通配符四大模块,对应不同使用场景。