Day27 | Java集合框架之List接口详解

昨天我们学习了Collection,今天我们看看它最常用的子接口之一------List。

List代表一个有序、可重复的元素集合,支持根据索引进行访问、插入、删除等操作。

常见的实现类包括ArrayList和LinkedList,它们在底层结构、性能特征和适用场景上有明显差异。

一、List接口概述

List接口是java.util包中的一个重要接口,它继承自Collection,定义了线性序列中元素的插入、删除、替换、查找等操作。

常用方法:

java 复制代码
public interface List extends Collection {
// 在末尾添加元素
boolean add(E e);

// 指定位置插入元素
void add(int index, E element);

// 获取指定索引的元素
E get(int index);

// 删除指定位置的元素
E remove(int index);

// 删除首次出现的指定元素
boolean remove(Object o);

// 返回元素首次出现的索引
int indexOf(Object o);

// 返回元素最后出现的索引
int lastIndexOf(Object o);

// 获取子列表
List subList(int fromIndex, int toIndex);

// 获取支持双向遍历的迭代器
ListIterator listIterator();
}

示例:

java 复制代码
package com.lazy.snail;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

/**
 * @ClassName Day27Demo
 * @Description TODO
 * @Author lazysnail
 * @Date 2025/6/24 10:51
 * @Version 1.0
 */
public class Day27Demo {
    public static void main(String[] args) {
        List strList = new ArrayList<>();

        // 添加元素
        strList.add(&#34;懒惰&#34;);
        strList.add(&#34;蜗牛&#34;);
        strList.add(&#34;学&#34;);
        strList.add(&#34;JAVA&#34;);
        System.out.println(&#34;初始列表:&#34; + strList);

        // 指定位置插入元素
        strList.add(2, &#34;不学&#34;);
        System.out.println(&#34;指定插入后列表:&#34; + strList);

        // 获取指定索引元素
        String str2 = strList.get(2);
        System.out.println(&#34;索引2处元素:&#34; + str2);

        // 删除指定索引元素
        strList.remove(2);
        System.out.println(&#34;删除索引2元素后列表:&#34; + strList);

        // 删除首次出现的元素
        strList.remove(&#34;懒惰&#34;);
        System.out.println(&#34;删除'懒惰'后列表:&#34; + strList);

        // 添加重复元素
        strList.add(&#34;蜗牛&#34;);
        strList.add(&#34;蜗牛&#34;);
        strList.add(&#34;蜗牛&#34;);
        strList.add(&#34;蜗牛&#34;);
        System.out.println(&#34;添加重复元素后列表:&#34; + strList);

        // 返回元素首次出现的索引
        int firstSnailIndex = strList.indexOf(&#34;蜗牛&#34;);
        System.out.println(&#34;第一个'蜗牛'的索引:&#34; + firstSnailIndex);

        // 返回元素最后出现的索引
        int lastSnailIndex = strList.lastIndexOf(&#34;蜗牛&#34;);
        System.out.println(&#34;最后一个'蜗牛'的索引:&#34; + lastSnailIndex);

        // 获取子列表(fromIndex 包含,toIndex 不包含)
        List sub = strList.subList(0, 3);
        System.out.println(&#34;子列表 [0,3):&#34; + sub);

        // 使用 ListIterator 双向遍历
        ListIterator iterator = strList.listIterator();
        System.out.println(&#34;正向遍历:&#34;);
        while (iterator.hasNext()) {
            System.out.println(&#34;→ &#34; + iterator.next());
        }

        System.out.println(&#34;反向遍历:&#34;);
        while (iterator.hasPrevious()) {
            System.out.println(&#34;← &#34; + iterator.previous());
        }
    }
}

二、ArrayList

第一节中的示例代码就是使用的ArrayList。

ArrayList是List接口最核心、最常用的实现类。

它的名字其实已经暴露了它大概的实现方式:Array(数组)。

1、底层结构

ArrayList内部封装了一个可以动态调整大小的Object[]数组。

java 复制代码
// 存储数据的数组
transient Object[] elementData;
// 当前元素个数
private int size;

2、添加元素

java 复制代码
public boolean add(E e) {
    modCount++;
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

每次添加元素的时候都会检查是不是需要扩容,扩容一般是原容量的 1.5 倍:

3、删除元素

由于是基于数组,插入和删除操作都涉及到元素的批量移动,效率为 O(n)。

4、ArrayList有什么优缺点

由于底层是连续的内存空间,通过索引get(i)访问元素的时候,CPU可以直接通过"起始地址 + i * 元素大小"的公式计算出地址,时间复杂度是O(1),所以查询速度非常快。

它使用的是尾插法,在没有触发扩容的情况下,往列表末尾添加元素只是简单的赋值操作,速度也很快。即使偶尔发生扩容,这种开销在大量操作中被平摊后,平均复杂度依然是O(1)。

但是如果在列表的中间进行增删,会导致该位置之后的所有元素进行批量移动,数据量越大,开销越大。

三、LinkedList

LinkedList底层基于双向链表。它由许多独立的节点串联而成,每个Node都存放着数据以及指向前后节点的引用。

1、底层结构

LinkedList是基于双向链表实现的,每个节点包含三个指针:

2、插入删除

因为链表结构,插入和删除某个节点只需调整前后节点的引用,不涉及数据移动,效率为O(1)。

但前提是已经定位到了目标节点,而定位的过程是O(n)。

3、查询

访问第n个元素的时候,需要从头或尾开始遍历,效率为O(n)。这个查询过程就是上面我们说的定位。

4、注意点

在实际开发中,我们基本上都是通过索引或遍历查找来定位元素,这个定位过程在LinkedList中是O(n) 的,它彻底拖垮了后续O(1)的修改优势。

add(index, e): O(n) 查找 + O(1) 修改 = 整体 O(n)

remove(Object o): O(n) 查找 + O(1) 修改 = 整体 O(n)

LinkedList在"按索引/对象增删"这个场景下,并没有比ArrayList快,有时候还因为它的内存不连续性(对CPU缓存不友好),实际运行得更慢。

所以,实际开发中,我们很少会用到LinkedList。

就连Josh Bloch也在《Effective Java》里说了,尽量避免使用LinkedList,除非你真的需要。

结语

今天,我们大致看了List接口两大实现的内部。

也聊了下ArrayList和LinkedList在实际开发中的应用场景。

我们在学习的过程中,不仅要会用,还要通过思考,具有基于场景和性能权衡的选型能力。

下一篇预告

Day28 | Java集合框架之Set接口详解

如果你觉得这系列文章对你有帮助,欢迎关注专栏,我们一起坚持下去!

更多文章请关注我的公众号《懒惰蜗牛工坊》

相关推荐
小码编匠2 小时前
WPF 实现高仿 Windows 通知提示框:工业级弹窗设计与实现
后端·c#·.net
未秃头的程序猿2 小时前
《Spring Boot MongoDB革命性升级!silky-mongodb-spring-boot-starter发布,开发效率暴增300%!》
后端·mongodb
a程序小傲2 小时前
美团二面:KAFKA能保证顺序读顺序写吗?
java·分布式·后端·kafka
墨笔之风2 小时前
数据库文档生成工具(PostgreSQL 适配版 - Java 8 兼容)
java·数据库·postgresql
a努力。2 小时前
网易Java面试被问:fail-safe和fail-fast
java·windows·后端·面试·架构
计算机毕设指导62 小时前
基于微信小程序的宠物走失信息管理系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·宠物
姜太小白2 小时前
【数据库】SQLite 时间加1天的方法总结
java·数据库·sqlite
Cache技术分享2 小时前
266. Java 集合 - ArrayList vs LinkedList 内存使用深度剖析
前端·后端
BBB努力学习程序设计2 小时前
Java异常处理机制:从基础到高级实践指南
java