1. 项目概述:用ArrayList构建电影管理系统
去年接手一个校园影院管理系统的重构项目时,我第一反应就是选用ArrayList作为核心数据结构。这个案例完美展示了如何用最基础的集合类型实现业务功能,特别适合刚学完集合框架的Java开发者练手。我们将创建一个包含电影增删改查、排序筛选等功能的控制台程序,核心就是两个类:Movie类封装电影属性,MovieOperator类处理业务逻辑。
ArrayList的随机访问特性在这里大放异彩——当我们需要根据索引快速检索影片时,时间复杂度能稳定在O(1)。相比LinkedList,这在频繁按位置查询的场景下性能优势明显。不过要注意,如果系统后期需要频繁在中间位置插入删除影片(比如插队新上映电影),可能需要考虑LinkedList。
关键设计原则:Movie类保持纯粹的数据封装,所有业务操作都集中在MovieOperator类。这符合单一职责原则,也是我见过太多新手容易踩的坑——把业务逻辑散落在各个POJO里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心类设计详解
2.1 Movie类的字段设计
java复制public class Movie {
private int id; // 影片唯一标识
private String name; // 片名(含系列后缀如《XXX2》)
private String director; // 导演(考虑多人情况用List<String>)
private double score; // 豆瓣评分(0-10分,保留1位小数)
private int duration; // 片长(分钟)
private List<String> actors = new ArrayList<>(); // 主演列表
private LocalDate releaseDate; // 上映日期
private String type; // 类型(科幻/动作等,可用枚举优化)
// 全参构造 + getter/setter省略...
}
几个设计细节值得注意:
- 主演用List而非数组,方便后续动态增减
- 评分用double而非BigDecimal,因无精确计算需求
- 日期用LocalDate而非Date,避免旧API的时区问题
- 类型字段后期可改为枚举,增强类型安全
2.2 MovieOperator类的核心方法
java复制public class MovieOperator {
private List<Movie> movies = new ArrayList<>(100); // 初始化容量
// 添加电影(防重复ID)
public void addMovie(Movie movie) {
if (getById(movie.getId()) != null) {
throw new IllegalArgumentException("电影ID重复");
}
movies.add(movie);
}
// ID查询(演示ArrayList遍历)
public Movie getById(int id) {
for (Movie m : movies) {
if (m.getId() == id) return m;
}
return null;
}
// 按评分降序排序
public void sortByScore() {
movies.sort((m1, m2) ->
Double.compare(m2.getScore(), m1.getScore()));
}
// 条件筛选(导演查询示例)
public List<Movie> filterByDirector(String director) {
return movies.stream()
.filter(m -> m.getDirector().contains(director))
.collect(Collectors.toList());
}
}
3. 关键业务实现技巧
3.1 高效删除方案的抉择
当需要根据ID删除电影时,新手常直接遍历调用remove:
java复制// 反例:性能陷阱
public void removeMovie(int id) {
for (Movie m : movies) {
if (m.getId() == id) {
movies.remove(m); // 可能引发ConcurrentModificationException
return;
}
}
}
更优方案是使用迭代器或倒序遍历:
java复制// 正例1:迭代器删除
Iterator<Movie> it = movies.iterator();
while (it.hasNext()) {
if (it.next().getId() == id) {
it.remove();
break;
}
}
// 正例2:倒序索引删除
for (int i = movies.size()-1; i >= 0; i--) {
if (movies.get(i).getId() == id) {
movies.remove(i);
break;
}
}
3.2 分页查询的实现
控制台系统也需要分页展示,核心是利用subList:
java复制public List<Movie> getPage(int pageNum, int pageSize) {
int fromIndex = (pageNum - 1) * pageSize;
if (fromIndex >= movies.size()) return Collections.emptyList();
int toIndex = Math.min(fromIndex + pageSize, movies.size());
return new ArrayList<>(movies.subList(fromIndex, toIndex));
}
踩坑提醒:直接返回subList会导致后续操作影响原列表,这里用new ArrayList包装生成独立副本
4. 性能优化实战记录
4.1 初始化容量设定
当已知电影数量级时,构造ArrayList时指定初始容量:
java复制// 预估存储500部电影
List<Movie> movies = new ArrayList<>(500);
这避免了多次扩容(默认扩容1.5倍,涉及数组拷贝)。测试数据显示,万级数据插入时能减少30%以上的时间。
4.2 批量操作优化
批量添加使用addAll:
java复制// 反例:逐条添加
newMovies.forEach(movies::add);
// 正例:批量添加
movies.addAll(newMovies);
批量删除使用removeAll或retainAll:
java复制// 删除特定类型电影
movies.removeAll(
movies.stream()
.filter(m -> m.getType().equals("烂片"))
.collect(Collectors.toList())
);
5. 典型问题排查实录
5.1 ConcurrentModificationException
java复制for (Movie m : movies) {
if (m.getScore() < 3) {
movies.remove(m); // 运行时抛出异常
}
}
解决方案:
- 使用迭代器删除
- 使用removeIf方法:
java复制movies.removeIf(m -> m.getScore() < 3);
5.2 排序稳定性问题
当多字段排序时,需要组合比较条件:
java复制// 先按类型升序,同类型按评分降序
movies.sort(Comparator
.comparing(Movie::getType)
.thenComparing(Comparator
.comparing(Movie::getScore).reversed())
);
6. 功能扩展方向
6.1 接入文件持久化
java复制// 保存到文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("movies.dat"))) {
oos.writeObject(movies);
}
// 从文件加载
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("movies.dat"))) {
movies = (List<Movie>) ois.readObject();
}
注意:Movie类需实现Serializable接口,且serialVersionUID要显式声明
6.2 简单推荐算法实现
基于类型相似度的推荐:
java复制public List<Movie> recommend(Movie baseMovie, int max) {
return movies.stream()
.filter(m -> !m.equals(baseMovie))
.sorted(Comparator
.comparingDouble(m -> similarity(baseMovie, m)))
.limit(max)
.collect(Collectors.toList());
}
private double similarity(Movie m1, Movie m2) {
double typeScore = m1.getType().equals(m2.getType()) ? 1 : 0;
double directorScore = m1.getDirector().equals(m2.getDirector()) ? 0.5 : 0;
return typeScore + directorScore;
}
这个项目让我深刻体会到,即使像ArrayList这样基础的集合类,在业务场景中也有大量细节需要考量。特别是在处理边界条件时,比如删除元素时的并发修改、大数据量时的扩容开销等,都是教科书上不会强调的实战经验。建议每个Java开发者都亲手实现一次这样的综合案例,会比单纯学习API文档收获大得多。
