Java 实例 – List 截取(千字长文)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 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+ 小伙伴加入学习 ,欢迎点击围观
二级标题:从书架到代码:List 是什么?子列表又是如何工作的?
在 Java 编程中,List
是一种有序的、可重复的数据集合,就像我们书架上排列整齐的书籍,每本书都有自己的位置索引。而“List 截取”则是从这个书架中,选取某一段连续的书籍,形成一个新的子列表。例如,假设书架上有 10 本书,截取第 3 到第 7 本,就会得到一个包含 5 本书的新列表。
为什么需要 List 截取?
在实际开发中,我们经常需要从庞大的数据集中提取特定范围的数据。例如:
- 分页查询:从数据库获取的 1000 条记录中,截取第 1 到第 10 条作为第一页的数据。
- 数据过滤:在用户输入的搜索词中,截取前 50 个字符用于显示摘要。
- 算法优化:在排序或遍历时,仅处理部分数据以提升性能。
二级标题:Java List 截取的核心方法:subList()
在 Java 标准库中,List
接口提供了 subList(int fromIndex, int toIndex)
方法,这是实现 List 截取的核心工具。
方法语法与参数说明
List<ElementType> subList = originalList.subList(fromIndex, toIndex);
fromIndex
:截取的起始索引(包含)。toIndex
:截取的结束索引(不包含)。- 返回值:返回一个新的
List
对象,它是原列表的“视图”而非副本。
关键特性:视图 vs 副本
subList()
返回的是原列表的视图,这意味着:
- 修改子列表会影响原列表:如果通过子列表添加或删除元素,原列表也会同步变化。
- 动态更新:当原列表的内容发生变化时,子列表会反映这些变化。
比喻:这就像在一张地图上用红笔圈出某个区域,地图本身并没有被复制,只是标记了一个观察的视角。
实例代码演示
以下是一个简单的代码示例:
import java.util.ArrayList;
import java.util.List;
public class ListSublistExample {
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("Java 编程思想");
books.add("算法导论");
books.add("设计模式");
books.add("数据结构");
books.add("Spring 框架");
// 截取索引 1 到 4 的元素(不包含 4)
List<String> subList = books.subList(1, 4);
System.out.println("子列表内容:" + subList);
// 输出:[算法导论, 设计模式, 数据结构]
// 修改子列表
subList.set(0, "算法精解");
// 检查原列表是否变化
System.out.println("修改后的原列表:" + books);
// 输出:[Java 编程思想, 算法精解, 设计模式, 数据结构, Spring 框架]
}
}
常见错误与解决方案
错误 1:索引越界
如果 fromIndex
或 toIndex
超出列表长度,会抛出 IndexOutOfBoundsException
。例如:
List<String> list = new ArrayList<>(List.of("A", "B"));
List<String> errorSubList = list.subList(3, 4); // 抛出异常
解决方案:在调用 subList()
前,先验证索引范围:
if (fromIndex >= 0 && toIndex <= list.size() && fromIndex < toIndex) {
// 安全调用 subList()
}
错误 2:误解子列表的生命周期
如果原列表被修改(例如通过 clear()
或 add()
),子列表可能变得不可用。例如:
List<String> original = new ArrayList<>(List.of("A", "B", "C"));
List<String> sub = original.subList(1, 2);
original.add("D"); // 此时 sub 可能失效
// 后续操作 sub 可能抛出 ConcurrentModificationException
解决方案:避免在截取后修改原列表的结构。若需修改,建议先将子列表转换为独立列表:
List<String> independentList = new ArrayList<>(sub);
二级标题:List 截取的扩展场景与最佳实践
场景 1:分页查询的实现
在 Web 开发中,分页是常见的需求。例如,从数据库查询 1000 条记录,每页显示 10 条:
public List<User> getPage(int pageNumber, int pageSize) {
List<User> allUsers = repository.findAll();
int fromIndex = (pageNumber - 1) * pageSize;
return allUsers.subList(fromIndex, fromIndex + pageSize);
}
注意:此代码假设 allUsers
已加载到内存中。实际开发中,应优先使用数据库分页(如 SQL 的 LIMIT
)以避免内存溢出。
场景 2:安全截取的封装工具类
为避免索引越界,可以封装一个安全的截取工具方法:
public static <T> List<T> safeSubList(List<T> list, int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex > list.size() || fromIndex >= toIndex) {
throw new IllegalArgumentException("无效的索引范围");
}
return list.subList(fromIndex, toIndex);
}
场景 3:结合流式处理过滤数据
通过结合 subList()
和 Stream API,可以高效处理数据:
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> evenNumbers = numbers.subList(1, 9)
.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// evenNumbers 包含 [2,4,6,8]
二级标题:性能对比:subList() 与复制操作
关键区别:时间复杂度
subList()
:时间复杂度为 O(1),因为它只是创建了一个对原列表的引用。- 复制操作(如
new ArrayList<>(original.subList(...))
):时间复杂度为 O(n),需要遍历所有元素。
适用场景
场景 | 推荐方法 | 原因 |
---|---|---|
需要频繁修改原列表 | subList() | 直接操作视图,无需复制数据 |
需要独立的副本 | 复制操作 | 避免原列表变化影响子列表 |
处理大数据集 | subList() + 流式处理 | 减少内存占用,提升性能 |
二级标题:List 截取的常见误区与解决方案
误区 1:“子列表是原列表的浅拷贝”
实际上,subList()
返回的是视图,而非拷贝。因此,修改子列表会直接修改原列表。
解决方案:
- 如果需要独立列表,使用
new ArrayList<>(subList)
。
误区 2:误用 subList()
的结束索引
例如,试图截取到列表末尾时,容易忘记结束索引应为 list.size()
:
List<String> lastThree = list.subList(list.size() - 3, list.size()); // 正确
List<String> wrong = list.subList(list.size() - 3, list.size() - 1); // 错误
误区 3:与并发修改的冲突
在多线程环境下,若多个线程同时修改原列表和子列表,可能导致 ConcurrentModificationException
。
解决方案:
- 使用线程安全的集合类(如
CopyOnWriteArrayList
)。 - 在读写操作前加锁。
二级标题:实战案例:实现动态范围查询
案例背景
假设有一个学生管理系统,需要根据分数范围筛选学生记录:
public class Student {
private String name;
private int score;
// 构造函数、Getter/Setter 略
}
public List<Student> getStudentsByScoreRange(
List<Student> allStudents,
int minScore,
int maxScore
) {
// 实现逻辑
}
解决方案
结合 subList()
和排序:
// 步骤 1:按分数排序
allStudents.sort(Comparator.comparingInt(s -> s.getScore()));
// 步骤 2:找到符合条件的起始和结束索引
int startIndex = 0;
int endIndex = allStudents.size();
// 找到第一个 >= minScore 的索引
while (startIndex < endIndex && allStudents.get(startIndex).getScore() < minScore) {
startIndex++;
}
// 找到第一个 > maxScore 的索引
while (endIndex > startIndex && allStudents.get(endIndex - 1).getScore() > maxScore) {
endIndex--;
}
// 步骤 3:截取子列表
return allStudents.subList(startIndex, endIndex);
优化点
- 预排序:如果列表频繁查询但较少修改,可提前维护一个有序列表。
- 二分查找:对于大型列表,可用
binarySearch
快速定位索引。
二级标题:结论:掌握 List 截取的底层逻辑与实践技巧
通过本文的讲解,我们深入理解了 subList()
方法的特性、使用场景以及常见问题的解决方案。在实际开发中,应根据需求选择合适的方法:
- 追求高效:直接使用
subList()
处理视图。 - 需要独立性:通过复制操作创建副本。
- 复杂场景:结合排序、流式处理等技术实现高级功能。
掌握 List 截取的核心原理,不仅能提升代码的可读性和性能,还能帮助开发者在数据处理、分页设计等场景中游刃有余。通过不断实践和优化,您将更好地驾驭 Java 中的集合框架,为更复杂的编程任务打下坚实基础。