第49章

Java List接口

详解List接口和常用实现类的特性、性能和使用场景

学习目标

List接口概述

List接口是Java集合框架中最重要的接口之一,它继承自Collection接口,表示一个有序的集合。List接口定义了一系列操作有序集合的方法,是ArrayList、LinkedList、Vector等实现类的基础。

有序性

List中的元素按照插入顺序存储,每个元素都有一个确定的位置索引。

可重复

List允许存储重复的元素,同一个对象可以在List中出现多次。

索引访问

支持通过索引直接访问、修改和删除指定位置的元素。

动态大小

List的大小可以动态增长和缩减,不需要预先指定容量。

List接口的常用方法

// 添加元素
boolean add(E e)                    // 在末尾添加元素
void add(int index, E element)      // 在指定位置插入元素

// 获取元素
E get(int index)                    // 获取指定位置的元素

// 修改元素
E set(int index, E element)         // 替换指定位置的元素

// 删除元素
E remove(int index)                 // 删除指定位置的元素
boolean remove(Object o)            // 删除指定对象

// 查找元素
int indexOf(Object o)               // 查找元素第一次出现的位置
int lastIndexOf(Object o)           // 查找元素最后一次出现的位置
boolean contains(Object o)          // 判断是否包含指定元素

// 其他操作
int size()                          // 获取元素个数
boolean isEmpty()                   // 判断是否为空
void clear()                        // 清空所有元素
List subList(int from, int to)   // 获取子列表

ArrayList详解

ArrayList是List接口最常用的实现类,基于动态数组实现,提供了高效的随机访问能力。

ArrayList的核心特性

  • 底层实现:基于Object[]数组,支持动态扩容
  • 随机访问:通过索引访问元素的时间复杂度为O(1)
  • 插入删除:在末尾操作效率高,中间操作需要移动元素
  • 内存效率:连续内存存储,空间利用率高
  • 线程安全:非线程安全,多线程环境需要外部同步

ArrayList扩容机制

// ArrayList扩容示例
ArrayList list = new ArrayList<>();  // 默认初始容量为10

// 当元素数量超过当前容量时,会触发扩容
// 新容量 = 旧容量 + (旧容量 >> 1)  // 即增长50%

// 建议:如果知道大概大小,可以指定初始容量
ArrayList optimizedList = new ArrayList<>(1000);

LinkedList详解

LinkedList基于双向链表实现,在插入和删除操作方面具有优势,同时实现了Deque接口,可以作为栈或队列使用。

LinkedList的核心特性

  • 底层实现:基于双向链表,每个节点包含数据和前后指针
  • 插入删除:在已知节点位置时,插入删除的时间复杂度为O(1)
  • 随机访问:需要从头或尾遍历,时间复杂度为O(n)
  • 内存开销:每个节点需要额外存储指针,内存开销较大
  • 双端队列:实现了Deque接口,支持栈和队列操作

LinkedList作为双端队列

LinkedList deque = new LinkedList<>();

// 作为栈使用(LIFO - 后进先出)
deque.push(1);    // 入栈
deque.push(2);
int top = deque.pop();  // 出栈

// 作为队列使用(FIFO - 先进先出)
deque.offer(10);  // 入队
deque.offer(20);
int front = deque.poll();  // 出队

// 双端操作
deque.offerFirst(100);  // 头部添加
deque.offerLast(200);   // 尾部添加
int first = deque.pollFirst();  // 头部取出
int last = deque.pollLast();    // 尾部取出

性能对比分析

不同的List实现类在各种操作上的性能表现差异很大,选择合适的实现类对程序性能至关重要。

操作类型 ArrayList LinkedList Vector 说明
随机访问 O(1) O(n) O(1) ArrayList和Vector基于数组,支持直接索引访问
尾部添加 O(1)* O(1) O(1)* *可能触发扩容,最坏情况O(n)
中间插入 O(n) O(1)** O(n) **如果已知节点位置
删除元素 O(n) O(1)** O(n) **如果已知节点位置
内存占用 LinkedList每个节点需要额外存储指针
线程安全 Vector的方法使用synchronized修饰

使用场景建议

ArrayList适用场景

  • 需要频繁随机访问元素
  • 主要在尾部添加/删除元素
  • 对内存使用效率要求高
  • 单线程环境或有外部同步
  • 读操作远多于写操作

LinkedList适用场景

  • 需要频繁在中间插入/删除元素
  • 实现栈、队列等数据结构
  • 不需要随机访问元素
  • 列表大小变化频繁
  • 需要双端队列功能

Vector适用场景

  • 需要线程安全的List
  • 兼容旧版本Java代码
  • 简单的多线程场景
  • 性能要求不高的应用
  • 遗留系统维护

完整代码示例

以下是一个综合性的List接口使用示例,展示了不同实现类的特性和使用方法:

import java.util.*;

/**
 * List接口综合示例
 * 演示ArrayList、LinkedList和Vector的使用
 */
public class ListComprehensiveExample {
    
    public static void main(String[] args) {
        // 演示List的基本特性
        demonstrateListFeatures();
        
        // 比较不同实现类的性能
        comparePerformance();
        
        // 演示实际应用场景
        demonstrateRealWorldUsage();
    }
    
    /**
     * 演示List接口的基本特性
     */
    private static void demonstrateListFeatures() {
        System.out.println("=== List基本特性演示 ===");
        
        List fruits = new ArrayList<>();
        
        // 1. 有序性:按插入顺序存储
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("橙子");
        System.out.println("插入顺序: " + fruits);
        
        // 2. 可重复:允许重复元素
        fruits.add("苹果"); // 重复元素
        System.out.println("允许重复: " + fruits);
        
        // 3. 索引访问:通过索引获取元素
        System.out.println("索引0的元素: " + fruits.get(0));
        System.out.println("索引2的元素: " + fruits.get(2));
        
        // 4. 动态大小
        System.out.println("当前大小: " + fruits.size());
        fruits.remove("香蕉");
        System.out.println("删除后大小: " + fruits.size());
    }
}
💻 查看完整代码 - 在线IDE体验

最佳实践建议

选择合适的实现类

  • 默认选择:大多数情况下使用ArrayList
  • 频繁插入删除:考虑使用LinkedList
  • 线程安全需求:使用Collections.synchronizedList()或CopyOnWriteArrayList
  • 性能优化:合理设置ArrayList的初始容量

常见陷阱和注意事项

  • 遍历时修改:避免在增强for循环中修改List,使用Iterator.remove()
  • 索引越界:访问元素前检查索引范围
  • null值处理:List允许null值,注意空指针异常
  • equals方法:自定义对象需要正确实现equals和hashCode方法

本章小结