跳到主要内容
Java 集合框架进阶——List 实现类深度解析与实战优化 | 极客日志
Java java 算法
Java 集合框架进阶——List 实现类深度解析与实战优化 综述由AI生成 Java 集合框架 List 实现类深度解析与实战优化。文章详细阐述了 ArrayList 动态数组扩容机制、索引访问优势,LinkedList 双向链表增删特性,以及 Vector 线程安全实现。对比遍历效率、内存占用及并发场景表现,提供选择策略与优化技巧,如初始化容量设定、避免并发修改异常。结合学生成绩管理系统实战案例,展示 List 实际应用与注意事项。
蓝绿部署 发布于 2026/2/20 更新于 2026/6/8 29 浏览Java 集合框架进阶——List 实现类深度解析与实战优化
一、章节学习目标与重点
1.1 学习目标
深入理解 ArrayList、LinkedList、Vector 三大 List 实现类的底层数据结构与设计原理
掌握各 List 实现类的核心方法源码逻辑,明确其性能差异的根源
能够根据实际业务场景选择合适的 List 实现类,并进行针对性优化
熟练解决 List 使用过程中的常见问题(如并发安全、扩容损耗、遍历效率等)
1.2 学习重点
ArrayList 扩容机制与索引操作的性能优化
LinkedList 双向链表结构与增删操作的底层实现
Vector 与 ArrayList 的区别及并发场景下的正确使用
List 遍历方式的效率对比与选择策略
大量数据处理时 List 的内存优化技巧
二、List 接口核心特性回顾
💡 List 作为 Java 集合框架中最常用的接口之一,继承自 Collection 接口,其核心特性是有序性 和可重复性 :
有序性:元素的插入顺序与遍历顺序保持一致,支持通过索引(index)直接访问元素
可重复性:允许存储多个相等的元素(equals() 方法返回 true)
核心方法:除了继承自 Collection 的 add()、remove()、size() 等方法外,List 新增了基于索引的操作方法,如 get(int index)、set(int index, E element)、add(int index, E element) 等
public interface List <E> extends Collection <E> {
E get (int index) ;
E set (int index, E element) ;
void add (int index, E element) ;
E remove (int index) ;
;
;
ListIterator<E> ;
}
int
indexOf
(Object o)
int
lastIndexOf
(Object o)
listIterator
()
⚠️ 注意:List 接口本身是抽象的,无法直接实例化,必须通过其实现类创建对象。Java 中最常用的 List 实现类有三个:ArrayList、LinkedList、Vector,它们各自基于不同的底层数据结构,适用于不同的业务场景。
三、ArrayList 深度解析:动态数组的设计与优化
3.1 底层数据结构 💡 ArrayList 的底层是动态数组(Object[]) ,通过数组扩容机制实现容量的动态增长。其核心成员变量如下:
public class ArrayList <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable {
transient Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10 ;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
}
当使用无参构造函数创建 ArrayList 时,初始 elementData 为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,首次添加元素时才会初始化容量为 10
当使用指定容量的构造函数时,若指定容量大于 0,则初始化 elementData 为对应容量的数组;若等于 0,则使用 EMPTY_ELEMENTDATA
3.2 核心方法源码解析
3.2.1 add() 方法:元素添加与扩容机制 ArrayList 的 add() 方法有两个重载版本:末尾添加和指定索引添加,核心逻辑是先检查容量,不足则扩容,再添加元素。
(1)末尾添加元素:add(E e) public boolean add (E e) {
ensureCapacityInternal(size + 1 );
elementData[size++] = e;
return true ;
}
private void ensureCapacityInternal (int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity (Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity (int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0 ) {
grow(minCapacity);
}
}
private void grow (int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1 );
if (newCapacity - minCapacity < 0 ) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0 ) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
初始容量为 10,每次扩容为原容量的 1.5 倍(通过位运算 oldCapacity >> 1 实现,效率高于除法)
扩容时会创建新数组,并将原数组元素复制到新数组,这个过程会消耗一定的时间和内存,因此建议在创建 ArrayList 时,根据预期元素数量指定初始容量,减少扩容次数
最大容量限制为 Integer.MAX_VALUE - 8,主要是为了避免某些虚拟机在数组头存储额外信息导致的溢出问题
(2)指定索引添加元素:add(int index, E element) public void add (int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1 );
System.arraycopy(elementData, index, elementData, index + 1 , size - index);
elementData[index] = element;
size++;
}
private void rangeCheckForAdd (int index) {
if (index > size || index < 0 ) {
throw new IndexOutOfBoundsException (outOfBoundsMsg(index));
}
}
⚠️ 注意:指定索引添加元素时,需要移动数组元素,时间复杂度为 O(n),元素越多,效率越低。因此,ArrayList 不适合频繁在中间位置插入元素的场景。
3.2.2 get() 方法:索引访问的高效实现 public E get (int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck (int index) {
if (index >= size) {
throw new IndexOutOfBoundsException (outOfBoundsMsg(index));
}
}
@SuppressWarnings("unchecked")
E elementData (int index) {
return (E) elementData[index];
}
💡 由于 ArrayList 底层是数组,数组支持随机访问,因此 get() 方法直接通过索引获取元素,效率极高,这是 ArrayList 最核心的优势之一。
3.2.3 remove() 方法:元素删除与数组收缩 public E remove (int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1 ;
if (numMoved > 0 ) {
System.arraycopy(elementData, index + 1 , elementData, index, numMoved);
}
elementData[--size] = null ;
return oldValue;
}
⚠️ 注意:删除非末尾元素时,需要移动数组元素,时间复杂度为 O(n);同时,ArrayList 删除元素后不会自动收缩数组容量(即 elementData 长度不变),只会减少 size 计数,若需要释放内存,可手动调用 trimToSize() 方法。
public void trimToSize () {
modCount++;
if (size < elementData.length) {
elementData = (size == 0 ) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}
3.3 ArrayList 性能分析与使用场景
3.3.1 性能特点 操作类型 时间复杂度 说明 末尾添加(add(E e)) O(1) 扩容时为 O(n),但扩容频率低(1.5 倍扩容) 指定索引添加/删除 O(n) 需要移动数组元素 索引访问(get/set) O(1) 随机访问,效率极高 查找元素(indexOf) O(n) 需遍历数组
3.3.2 适用场景
频繁进行索引访问(get/set 操作)的场景
元素添加/删除主要在集合末尾的场景
不需要线程安全的场景(ArrayList 是非线程安全的)
3.3.3 性能优化技巧
List<String> list = new ArrayList <>(1000 );
List<String> list = new ArrayList <>();
💡 技巧 2:避免在中间位置频繁添加/删除元素,若需频繁修改,考虑使用 LinkedList
💡 技巧 3:遍历 ArrayList 时,优先使用普通 for 循环或增强 for 循环(foreach),效率高于迭代器(Iterator)
ArrayList<String> list = new ArrayList <>();
for (int i = 0 ; i < list.size(); i++) {
String element = list.get(i);
}
for (String element : list) {
}
💡 技巧 4:大量数据处理后,若集合不再添加元素,调用 trimToSize() 释放空闲内存
List<LargeObject> list = new ArrayList <>(10000 );
list.trimToSize();
四、LinkedList 深度解析:双向链表的设计与实现
4.1 底层数据结构 💡 LinkedList 的底层是双向链表 ,每个节点(Node)包含前驱节点引用、后继节点引用和元素值。其核心成员变量如下:
public class LinkedList <E> extends AbstractSequentialList <E> implements List <E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0 ;
transient Node<E> first;
transient Node<E> last;
private static class Node <E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this .prev = prev;
this .item = element;
this .next = next;
}
}
}
first(头节点) last (尾节点)
↓ ↓
[prev:null, item:A, next →] ←→ [prev←, item:B, next →] ←→ ... ←→ [prev←, item:Z, next :null]
头节点的 prev 为 null,尾节点的 next 为 null
每个节点都能通过 prev 和 next 引用访问前后节点,实现双向遍历
与 ArrayList 不同,LinkedList 不需要数组扩容,因为链表节点可以动态创建
4.2 核心方法源码解析
4.2.1 add() 方法:节点添加机制
(1)末尾添加元素:add(E e) public boolean add (E e) {
linkLast(e);
return true ;
}
void linkLast (E e) {
final Node<E> l = last;
final Node<E> newNode = new Node <>(l, e, null );
last = newNode;
if (l == null ) {
first = newNode;
} else {
l.next = newNode;
}
size++;
modCount++;
}
💡 末尾添加元素时,只需创建新节点并更新尾节点引用,时间复杂度为 O(1),无需移动元素,效率极高。
(2)指定索引添加元素:add(int index, E element) public void add (int index, E element) {
checkPositionIndex(index);
if (index == size) {
linkLast(element);
} else {
linkBefore(element, node(index));
}
}
Node<E> node (int index) {
if (index < (size >> 1 )) {
Node<E> x = first;
for (int i = 0 ; i < index; i++) {
x = x.next;
}
return x;
} else {
Node<E> x = last;
for (int i = size - 1 ; i > index; i--) {
x = x.prev;
}
return x;
}
}
void linkBefore (E e, Node<E> succ) {
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node <>(pred, e, succ);
succ.prev = newNode;
if (pred == null ) {
first = newNode;
} else {
pred.next = newNode;
}
size++;
modCount++;
}
先通过 node() 方法找到索引位置的节点,该方法通过二分思想优化遍历效率,时间复杂度为 O(n/2) = O(n)
再通过 linkBefore() 方法插入新节点,仅需修改前后节点的引用,时间复杂度为 O(1)
整体时间复杂度为 O(n),但比 ArrayList 在中间位置添加元素的效率高(ArrayList 需移动数组元素,LinkedList 仅需修改引用)
4.2.2 get() 方法:索引访问的实现 public E get (int index) {
checkElementIndex(index);
return node(index).item;
}
⚠️ 注意:LinkedList 没有数组那样的随机访问能力,get() 方法需要通过 node() 方法遍历链表找到目标节点,时间复杂度为 O(n),因此索引访问效率远低于 ArrayList。
4.2.3 remove() 方法:节点删除机制 public E remove (int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink (Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null ) {
first = next;
} else {
prev.next = next;
x.prev = null ;
}
if (next == null ) {
last = prev;
} else {
next.prev = prev;
x.next = null ;
}
x.item = null ;
size--;
modCount++;
return element;
}
💡 删除节点时,仅需修改前后节点的引用,断开与目标节点的连接,时间复杂度为 O(1)(找到目标节点的时间复杂度为 O(n)),因此 LinkedList 适合频繁添加/删除元素的场景。
4.3 LinkedList 性能分析与使用场景
4.3.1 性能特点 操作类型 时间复杂度 说明 末尾/头部添加/删除 O(1) 直接修改头尾节点引用 指定索引添加/删除 O(n) 遍历找到目标节点(O(n))+ 修改引用(O(1)) 索引访问(get/set) O(n) 需遍历链表 查找元素(indexOf) O(n) 需遍历链表
4.3.2 适用场景
频繁在集合中间位置添加/删除元素的场景
元素添加/删除主要在头部或尾部的场景(LinkedList 实现了 Deque 接口,可作为双端队列使用)
不需要频繁进行索引访问的场景
4.3.3 LinkedList 与 ArrayList 核心区别对比 特性 ArrayList LinkedList 底层结构 动态数组 双向链表 索引访问效率 O(1)(高效) O(n)(低效) 中间位置增删效率 O(n)(低效,需移动元素) O(n)(较高效,仅需遍历 + 修改引用) 末尾增删效率 O(1)(扩容时 O(n)) O(1)(稳定高效) 内存占用 连续内存,可能有空闲空间 非连续内存,每个节点额外存储前后引用 线程安全 非线程安全 非线程安全 实现接口 List、RandomAccess List、Deque、Queue
4.4 LinkedList 实用技巧 💡 技巧 1:利用 LinkedList 实现双端队列(Deque)功能
Deque<String> deque = new LinkedList <>();
deqe.addFirst("A" );
deqe.addLast("B" );
String first = deque.getFirst();
String last = deque.getLast();
deqe.removeFirst();
deqe.removeLast();
💡 技巧 2:遍历 LinkedList 时,优先使用迭代器(Iterator)或增强 for 循环,避免使用普通 for 循环(频繁调用 get() 方法,效率极低)
LinkedList<String> list = new LinkedList <>();
for (String element : list) {
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
}
for (int i = 0 ; i < list.size(); i++) {
String element = list.get(i);
}
五、Vector 解析:线程安全的动态数组
5.1 底层数据结构与核心特性 💡 Vector 是 Java 早期提供的 List 实现类,底层同样是动态数组(Object[]) ,与 ArrayList 结构相似,但核心区别是线程安全 ——Vector 的所有核心方法都被 synchronized 关键字修饰,确保多线程环境下的操作原子性。
public class Vector <E> extends AbstractList <E> implements List <E>, RandomAccess, Cloneable, java.io.Serializable {
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;
private static final int DEFAULT_CAPACITY = 10 ;
}
5.2 核心方法与 ArrayList 的区别
5.2.1 线程安全机制 Vector 的核心方法(add、get、remove 等)都添加了 synchronized 修饰,例如:
public synchronized boolean add (E e) {
modCount++;
ensureCapacityHelper(elementCount + 1 );
elementData[elementCount++] = e;
return true ;
}
public synchronized E get (int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException (index);
}
return elementData(index);
}
而 ArrayList 的方法没有 synchronized 修饰,是非线程安全的,多线程环境下并发修改可能导致 ConcurrentModificationException。
5.2.2 扩容机制 Vector 的扩容机制与 ArrayList 不同,主要体现在扩容增量上:
private void grow (int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0 ) ? capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0 ) {
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0 ) {
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList 每次扩容为原容量的 1.5 倍
Vector 若指定了扩容增量(capacityIncrement),则每次扩容增加 capacityIncrement;若未指定(默认 0),则每次扩容为原容量的 2 倍
5.2.3 构造函数 Vector 提供了三个构造函数,支持指定初始容量和扩容增量:
public Vector () {
this (DEFAULT_CAPACITY, 0 );
}
public Vector (int initialCapacity) {
this (initialCapacity, 0 );
}
public Vector (int initialCapacity, int capacityIncrement) {
super ();
if (initialCapacity < 0 ) {
throw new IllegalArgumentException ("Illegal Capacity: " + initialCapacity);
}
this .elementData = new Object [initialCapacity];
this .capacityIncrement = capacityIncrement;
}
5.3 Vector 的使用场景与注意事项
5.3.1 适用场景
多线程环境下需要线程安全的 List 操作场景
不需要频繁扩容,且能明确扩容增量的场景
5.3.2 注意事项 ⚠️ 注意 1:Vector 的线程安全是通过 synchronized 修饰方法实现的,属于粗粒度锁,并发性能较低。若需要更高的并发性能,推荐使用 Collections.synchronizedList(new ArrayList<>()) 或 CopyOnWriteArrayList(JUC 提供的线程安全 List)。
List<String> syncList = Collections.synchronizedList(new ArrayList <>());
List<String> cowList = new CopyOnWriteArrayList <>();
⚠️ 注意 2:Vector 与 ArrayList 相比,除了线程安全外,其他性能(如索引访问、扩容效率)基本一致,但由于 synchronized 的开销,单线程环境下不推荐使用 Vector,优先选择 ArrayList。
⚠️ 注意 3:Vector 的扩容增量若设置不当,可能导致内存浪费。例如,未指定扩容增量时,每次扩容为 2 倍,若元素数量增长缓慢,会产生大量空闲内存。
六、List 常见问题与解决方案
6.1 并发修改异常(ConcurrentModificationException)
6.1.1 问题现象 多线程环境下修改 List(如一边遍历一边添加/删除元素),或单线程环境下使用迭代器遍历 List 时修改 List,会抛出 ConcurrentModificationException。
List<String> list = new ArrayList <>();
list.add("A" );
list.add("B" );
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("A" )) {
list.remove(element);
}
}
6.1.2 问题原因 List 的迭代器(Iterator)是快速失败(fail-fast)的,迭代器创建时会记录 List 的修改次数(modCount)。遍历过程中,若 List 的修改次数(modCount)与迭代器记录的修改次数(expectedModCount)不一致,会立即抛出异常,防止迭代器继续遍历不一致的数据。
6.1.3 解决方案 💡 方案 1:使用迭代器的 remove() 方法修改 List
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element.equals("A" )) {
iterator.remove();
}
}
💡 方案 2:使用增强 for 循环遍历(底层基于迭代器,但不支持在遍历中修改,需结合方案 1)
💡 方案 3:多线程环境下使用线程安全的 List(如 CopyOnWriteArrayList)
List<String> list = new CopyOnWriteArrayList <>();
list.add("A" );
list.add("B" );
new Thread (() -> {
for (String element : list) {
System.out.println(element);
}
}).start();
new Thread (() -> {
list.remove("A" );
}).start();
6.2 List 遍历方式效率对比
6.2.1 遍历方式分类 List 常见的遍历方式有 4 种:普通 for 循环、增强 for 循环、迭代器遍历、forEach 方法(Java 8+)。
6.2.2 效率对比测试
public class ListTraversalTest {
public static void main (String[] args) {
List<Integer> arrayList = new ArrayList <>(1000000 );
for (int i = 0 ; i < 1000000 ; i++) {
arrayList.add(i);
}
List<Integer> linkedList = new LinkedList <>();
for (int i = 0 ; i < 1000000 ; i++) {
linkedList.add(i);
}
System.out.println("ArrayList 遍历效率测试:" );
testTraversal(arrayList);
System.out.println("\nLinkedList 遍历效率测试:" );
testTraversal(linkedList);
}
private static void testTraversal (List<Integer> list) {
long start, end;
start = System.currentTimeMillis();
for (int i = 0 ; i < list.size(); i++) {
list.get(i);
}
end = System.currentTimeMillis();
System.out.println("普通 for 循环:" + (end - start) + "ms" );
start = System.currentTimeMillis();
for (int num : list) {
}
end = System.currentTimeMillis();
System.out.println("增强 for 循环:" + (end - start) + "ms" );
start = System.currentTimeMillis();
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
end = System.currentTimeMillis();
System.out.println("迭代器遍历:" + (end - start) + "ms" );
start = System.currentTimeMillis();
list.forEach(num -> {
});
end = System.currentTimeMillis();
System.out.println("forEach 方法:" + (end - start) + "ms" );
}
}
6.2.3 测试结果与结论 遍历方式 ArrayList(100 万条) LinkedList(100 万条) 普通 for 循环 1ms 8923ms(极差) 增强 for 循环 3ms 4ms 迭代器遍历 2ms 3ms forEach 方法 4ms 5ms
ArrayList 优先使用普通 for 循环(索引访问效率最高),其次是增强 for 循环和迭代器
LinkedList 严禁使用普通 for 循环(频繁 get(i) 导致 O(n²) 时间复杂度),优先使用增强 for 循环、迭代器或 forEach 方法
Java 8+ 的 forEach 方法(基于函数式接口)语法简洁,效率与增强 for 循环接近,推荐使用
6.3 List 内存优化技巧
6.3.1 初始化时指定初始容量 无论是 ArrayList 还是 Vector,初始化时指定合理的初始容量,都能减少扩容次数,降低内存拷贝开销。
6.3.2 及时清理无用元素,帮助 GC 对于存储大量对象的 List,不再使用的元素应及时移除(remove() 方法),避免对象引用无法释放,导致内存泄漏。
6.3.3 使用 trimToSize() 释放空闲内存 ArrayList 和 Vector 都提供了 trimToSize() 方法,可将数组容量收缩为实际元素个数,释放未使用的空闲内存,适用于大量数据处理后不再添加元素的场景。
6.3.4 避免使用 LinkedList 存储大量数据 LinkedList 每个节点额外存储 prev 和 next 引用,内存占用比 ArrayList 高,对于大量数据存储,优先选择 ArrayList。
七、实战案例:基于 List 的学生成绩管理系统
7.1 需求分析
添加学生成绩(学号、姓名、科目、成绩)
根据学号查询学生所有成绩
根据科目查询该科目所有学生成绩
修改学生某科目的成绩
删除学生某科目的成绩
统计某科目成绩的平均分、最高分、最低分
7.2 设计思路
定义 StudentScore 类封装学生成绩信息
使用 ArrayList 存储所有成绩数据(频繁查询和修改,适合索引访问)
提供工具类实现查询、修改、删除、统计等功能
7.3 代码实现
7.3.1 学生成绩实体类(StudentScore.java)
public class StudentScore {
private String studentId;
private String studentName;
private String subject;
private int score;
public StudentScore (String studentId, String studentName, String subject, int score) {
this .studentId = studentId;
this .studentName = studentName;
this .subject = subject;
if (score < 0 || score > 100 ) {
throw new IllegalArgumentException ("成绩必须在 0-100 之间" );
}
this .score = score;
}
public String getStudentId () { return studentId; }
public void setStudentId (String studentId) { this .studentId = studentId; }
public String getStudentName () { return studentName; }
public void setStudentName (String studentName) { this .studentName = studentName; }
public String getSubject () { return subject; }
public void setSubject (String subject) { this .subject = subject; }
public int getScore () { return score; }
public void setScore (int score) {
if (score < 0 || score > 100 ) {
throw new IllegalArgumentException ("成绩必须在 0-100 之间" );
}
this .score = score;
}
@Override
public String toString () {
return "StudentScore{" +
"学号='" + studentId + '\'' +
", 姓名='" + studentName + '\'' +
", 科目='" + subject + '\'' +
", 成绩=" + score + '}' ;
}
}
7.3.2 成绩管理工具类(ScoreManager.java) import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class ScoreManager {
private List<StudentScore> scoreList = new ArrayList <>();
public void addScore (StudentScore score) {
if (score == null ) {
throw new IllegalArgumentException ("成绩对象不能为空" );
}
scoreList.add(score);
System.out.println("添加成功:" + score);
}
public List<StudentScore> queryScoresByStudentId (String studentId) {
if (studentId == null || studentId.trim().isEmpty()) {
throw new IllegalArgumentException ("学号不能为空" );
}
return scoreList.stream()
.filter(score -> studentId.equals(score.getStudentId()))
.collect(Collectors.toList());
}
public List<StudentScore> queryScoresBySubject (String subject) {
if (subject == null || subject.trim().isEmpty()) {
throw new IllegalArgumentException ("科目不能为空" );
}
return scoreList.stream()
.filter(score -> subject.equals(score.getSubject()))
.collect(Collectors.toList());
}
public boolean updateScore (String studentId, String subject, int newScore) {
if (studentId == null || studentId.trim().isEmpty()) {
throw new IllegalArgumentException ("学号不能为空" );
}
if (subject == null || subject.trim().isEmpty()) {
throw new IllegalArgumentException ("科目不能为空" );
}
if (newScore < 0 || newScore > 100 ) {
throw new IllegalArgumentException ("成绩必须在 0-100 之间" );
}
for (StudentScore score : scoreList) {
if (studentId.equals(score.getStudentId()) && subject.equals(score.getSubject())) {
score.setScore(newScore);
System.out.println("修改成功:学号=" + studentId + ",科目=" + subject + ",新成绩=" + newScore);
return true ;
}
}
System.out.println("修改失败:未找到学号=" + studentId + ",科目=" + subject + "的成绩" );
return false ;
}
public boolean deleteScore (String studentId, String subject) {
if (studentId == null || studentId.trim().isEmpty()) {
throw new IllegalArgumentException ("学号不能为空" );
}
if (subject == null || subject.trim().isEmpty()) {
throw new IllegalArgumentException ("科目不能为空" );
}
for (int i = 0 ; i < scoreList.size(); i++) {
StudentScore score = scoreList.get(i);
if (studentId.equals(score.getStudentId()) && subject.equals(score.getSubject())) {
scoreList.remove(i);
System.out.println("删除成功:学号=" + studentId + ",科目=" + subject);
return true ;
}
}
System.out.println("删除失败:未找到学号=" + studentId + ",科目=" + subject + "的成绩" );
return false ;
}
public double [] statSubjectScores(String subject) {
if (subject == null || subject.trim().isEmpty()) {
throw new IllegalArgumentException ("科目不能为空" );
}
List<StudentScore> subjectScores = queryScoresBySubject(subject);
if (subjectScores.isEmpty()) {
System.out.println("统计失败:科目=" + subject + "无成绩数据" );
return null ;
}
int total = 0 ;
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (StudentScore score : subjectScores) {
int s = score.getScore();
total += s;
if (s > max) {
max = s;
}
if (s < min) {
min = s;
}
}
double average = (double ) total / subjectScores.size();
double [] result = new double [3 ];
result[0 ] = average;
result[1 ] = max;
result[2 ] = min;
System.out.println("科目=" + subject + " 统计结果:" );
System.out.printf("平均分:%.2f,最高分:%d,最低分:%d%n" , average, max, min);
return result;
}
public List<StudentScore> getAllScores () {
return new ArrayList <>(scoreList);
}
}
7.3.3 测试类(ScoreManagerTest.java)
public class ScoreManagerTest {
public static void main (String[] args) {
ScoreManager scoreManager = new ScoreManager ();
System.out.println("=== 1. 添加学生成绩 ===" );
scoreManager.addScore(new StudentScore ("001" , "张三" , "Java" , 90 ));
scoreManager.addScore(new StudentScore ("001" , "张三" , "MySQL" , 85 ));
scoreManager.addScore(new StudentScore ("002" , "李四" , "Java" , 95 ));
scoreManager.addScore(new StudentScore ("002" , "李四" , "MySQL" , 88 ));
scoreManager.addScore(new StudentScore ("003" , "王五" , "Java" , 80 ));
System.out.println();
System.out.println("=== 2. 根据学号查询成绩(学号=001) ===" );
List<StudentScore> zhangSanScores = scoreManager.queryScoresByStudentId("001" );
zhangSanScores.forEach(System.out::println);
System.out.println();
System.out.println("=== 3. 根据科目查询成绩(科目=Java) ===" );
List<StudentScore> javaScores = scoreManager.queryScoresBySubject("Java" );
javaScores.forEach(System.out::println);
System.out.println();
System.out.println("=== 4. 修改成绩 ===" );
scoreManager.updateScore("001" , "Java" , 92 );
System.out.println();
System.out.println("=== 5. 删除成绩 ===" );
scoreManager.deleteScore("003" , "Java" );
System.out.println();
System.out.println("=== 6. 统计科目成绩(科目=Java) ===" );
scoreManager.statSubjectScores("Java" );
System.out.println();
System.out.println("=== 7. 查看所有成绩 ===" );
scoreManager.getAllScores().forEach(System.out::println);
}
}
7.4 案例总结 ✅ 本案例基于 ArrayList 实现了学生成绩管理系统,充分利用了 ArrayList 索引访问高效、修改便捷的优势,同时解决了 List 使用过程中的常见问题:
输入合法性校验:避免无效数据添加到 List 中
并发修改异常:使用普通 for 循环遍历删除元素,避免迭代器遍历期间修改 List
数据安全性:getAllScores() 方法返回 List 副本,防止外部直接修改内部数据
查询效率:使用 Stream API 简化查询逻辑,同时保证效率
✅ 若系统需要支持频繁添加/删除成绩记录,可将 ArrayList 替换为 LinkedList,但需注意遍历方式的选择(避免普通 for 循环);若系统需要在多线程环境下运行,可将 ArrayList 替换为 CopyOnWriteArrayList,确保线程安全。
八、本章小结 本章深入解析了 Java 集合框架中三大 List 实现类(ArrayList、LinkedList、Vector)的底层数据结构、核心方法源码、性能特点及适用场景,通过实战案例展示了 List 的实际应用,同时总结了 List 使用过程中的常见问题与优化技巧。
ArrayList 基于动态数组,索引访问高效,适合频繁查询和末尾增删场景
LinkedList 基于双向链表,增删元素高效(尤其是头尾操作),适合频繁修改场景
Vector 是线程安全的动态数组,并发性能较低,单线程环境不推荐使用
选择 List 实现类的核心原则:根据操作类型(查询/增删)和线程安全需求决定
性能优化关键:初始化指定容量、选择合适的遍历方式、避免并发修改异常
通过本章学习,读者应能根据实际业务场景灵活选择 List 实现类,并进行针对性优化,提升 Java 程序的性能和稳定性。下一章将深入学习 Java 集合框架中的 Set 接口及其实现类。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online