Java 实例 – 获取链表(LinkedList)的第一个和最后一个元素(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程中,链表(LinkedList)作为一种动态数据结构,因其灵活的插入和删除操作,被广泛应用于需要频繁修改序列的场景。然而,对于刚接触 Java 的开发者而言,如何高效地获取链表中的第一个和最后一个元素,可能是理解其核心功能的关键一步。本文将通过实例讲解这一操作的具体实现方法,结合代码示例与性能分析,帮助读者掌握链表操作的底层逻辑,并理解不同方法的适用场景。


一、链表(LinkedList)基础概念与特性

1.1 链表的基本定义

链表是由一系列节点(Node)组成的线性数据结构,每个节点包含数据域(Data)和指向下一个节点的引用(Next)。这种结构类似于火车车厢的连接方式:每个车厢(节点)通过车钩(引用)与前后车厢相连,形成一条可动态扩展的序列。

Java 中的 LinkedList 类实现了 List 接口,因此具备列表的基本功能(如索引访问、遍历等),同时支持在头部、尾部或中间快速插入和删除元素,这得益于其动态的内存分配机制。

1.2 链表的核心操作方法

LinkedList 提供了多种方法来访问和操作元素,其中与获取首尾元素直接相关的包括:

  • getFirst():获取链表的第一个元素。
  • getLast():获取链表的最后一个元素。
  • get(0)get(size() - 1):通过索引访问首尾元素。

这些方法的底层实现原理不同,性能表现也存在差异,后续章节将详细对比分析。


二、获取链表的第一个元素

2.1 使用 getFirst() 方法

getFirst()LinkedList 类的特有方法,直接返回链表头部的元素。其底层逻辑是直接访问链表的 first 节点,因此时间复杂度为 O(1)

代码示例:

import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        LinkedList<String> list = new LinkedList<>();
        list.add("元素1");
        list.add("元素2");
        list.add("元素3");

        // 获取第一个元素
        String firstElement = list.getFirst();
        System.out.println("第一个元素是:" + firstElement); // 输出:元素1
    }
}

2.2 异常处理:空链表的情况

若链表为空时调用 getFirst(),会抛出 NoSuchElementException。因此,在实际开发中需要添加异常处理逻辑:

try {
    String first = list.getFirst();
} catch (NoSuchElementException e) {
    System.out.println("链表为空,无法获取第一个元素");
}

2.3 替代方案:通过索引 get(0)

LinkedList 也支持通过索引 0 访问第一个元素:

String firstElement = list.get(0);

但需注意,虽然 get(0) 的时间复杂度同样是 O(1)(因为链表维护了 first 引用),但直接使用 getFirst() 更直观且符合面向对象的设计原则。


三、获取链表的最后一个元素

3.1 使用 getLast() 方法

类似 getFirst()getLast() 方法直接返回链表的尾部元素。其底层通过访问 last 节点实现,时间复杂度为 O(1)

代码示例:

// 假设链表已初始化并添加元素
String lastElement = list.getLast();
System.out.println("最后一个元素是:" + lastElement); // 输出:元素3

3.2 空链表的异常处理

getFirst() 类似,若链表为空时调用 getLast(),同样会抛出 NoSuchElementException,需添加对应的异常捕获:

try {
    String last = list.getLast();
} catch (NoSuchElementException e) {
    System.out.println("链表为空,无法获取最后一个元素");
}

3.3 替代方案:通过索引 get(size() - 1)

另一种方法是通过计算链表长度 size(),再访问索引 size() - 1

String lastElement = list.get(list.size() - 1);

但需注意:

  • 时间复杂度size() 方法本身是 O(1),但若链表未维护 size 变量(如某些自定义链表实现),可能需要遍历链表导致 O(n)
  • 可读性:直接使用 getLast() 更清晰,且避免了索引计算可能引入的错误(如链表为空时的负索引)。

四、性能对比与最佳实践

4.1 时间复杂度分析

方法获取第一个元素获取最后一个元素
getFirst() / getLast()O(1)O(1)
get(0) / get(size()-1)O(1)O(n) (理论值)

getLast() 的时间复杂度在 Java 的 LinkedList 实现中为 O(1),因为链表内部维护了 last 指针。而 get(size()-1) 理论上需要遍历链表,但实际由于 LinkedListget 方法通过双向遍历优化(从头或尾开始遍历,取较短路径),其性能可能接近 O(1),但并非严格保证。

4.2 推荐实践

  • 优先使用 getFirst()getLast():直接、高效且语义明确。
  • 避免直接操作索引:除非链表的长度变化频繁,需动态计算位置。
  • 添加空检查:通过 isEmpty() 方法提前判断链表是否为空,避免异常。

五、综合案例:学生信息管理

5.1 场景描述

假设需要管理学生名单,需快速获取第一个(最早加入)和最后一个(最新加入)学生的信息。

5.2 完整代码实现

import java.util.LinkedList;

public class StudentManager {
    private LinkedList<String> students = new LinkedList<>();

    public void addStudent(String studentName) {
        students.add(studentName);
    }

    public String getFirstStudent() {
        if (students.isEmpty()) {
            return "当前无学生信息";
        }
        return students.getFirst();
    }

    public String getLastStudent() {
        if (students.isEmpty()) {
            return "当前无学生信息";
        }
        return students.getLast();
    }

    public static void main(String[] args) {
        StudentManager manager = new StudentManager();
        manager.addStudent("张三");
        manager.addStudent("李四");
        manager.addStudent("王五");

        System.out.println("第一个学生:" + manager.getFirstStudent()); // 张三
        System.out.println("最后一个学生:" + manager.getLastStudent()); // 王五
    }
}

5.3 扩展思考

若需获取倒数第二个元素,可结合索引与 size() 方法:

int lastIndex = students.size() - 1;
String secondLast = students.get(lastIndex - 1); // 假设链表长度≥2

但需注意边界条件,避免索引越界。


六、常见问题与解答

6.1 为什么 LinkedListget 方法时间复杂度是 O(n)?

虽然 LinkedListget(0) 是 O(1),但访问中间元素(如 get(100))需要从头或尾遍历,导致最坏时间复杂度为 O(n)。因此,若频繁按索引访问元素,应优先使用 ArrayList

6.2 如何判断链表是否为空?

使用 isEmpty() 方法:

if (list.isEmpty()) {
    // 处理空链表逻辑
}

6.3 LinkedList 是否支持多线程环境?

默认情况下,LinkedList 不是线程安全的。若需多线程操作,可通过 Collections.synchronizedList() 包装,或改用 CopyOnWriteArrayList


七、结论

通过本文的讲解,读者应能掌握在 Java 中通过 LinkedList 获取首尾元素的多种方法,并理解其性能差异与适用场景。无论是通过 getFirst()getLast() 的直接调用,还是结合索引与 size() 的灵活操作,关键在于根据实际需求选择最简洁高效的方案。建议在开发中遵循以下原则:

  1. 优先使用特有方法(如 getFirst()),提升代码可读性与性能;
  2. 提前检查空链表,避免运行时异常;
  3. 结合场景选择数据结构,如频繁索引访问时选择 ArrayList

通过实践与对比,开发者将更熟练地驾驭链表这一灵活而强大的工具,为复杂业务场景提供高效解决方案。

最新发布