Java 实例 – 获取链表(LinkedList)的第一个和最后一个元素(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 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)
理论上需要遍历链表,但实际由于 LinkedList
的 get
方法通过双向遍历优化(从头或尾开始遍历,取较短路径),其性能可能接近 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 为什么 LinkedList
的 get
方法时间复杂度是 O(n)?
虽然 LinkedList
的 get(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()
的灵活操作,关键在于根据实际需求选择最简洁高效的方案。建议在开发中遵循以下原则:
- 优先使用特有方法(如
getFirst()
),提升代码可读性与性能; - 提前检查空链表,避免运行时异常;
- 结合场景选择数据结构,如频繁索引访问时选择
ArrayList
。
通过实践与对比,开发者将更熟练地驾驭链表这一灵活而强大的工具,为复杂业务场景提供高效解决方案。