课程内容:包装类(Wrapper Classes)、集合框架入门(Collection Framework)、ArrayList 底层实现、泛型(Generics)
涉及前序知识:数组、面向对象、构造方法、包装类、ArrayList 基本使用、泛型基础
提交方式:在
提交\姓名\目录下创建对应的 Java 源文件截止时间:下次上课前
| 项目 | 要求 |
|---|---|
| 包名 | 必须按题目指定的包名创建(全小写) |
| 类名 | 自由命名,但需见名知意 |
| 测试类 | 建议命名为 TestXxx 或 XxxTest |
| 文件名 | 必须与类名一致 |
提交\
└─ 张三\
├─ p1_arraylist_source\ ← 第 1~3 题的包
│ ├── ArrayListSourceAnalysis.java ← 源码注释分析(含所有方法)
│ └── ArrayListSourceAnalysis.md ← 思考题回答
└─ p2_myarraylist\ ← 第 4 题(选做)
├── MyArrayList.java ← 手写简化版 ArrayList
└── MyArrayListTest.java ← 测试类
本次作业的目的是让你深入阅读 ArrayList 的源码,理解其底层实现机制。请根据题目要求找到 JDK 中 ArrayList.java 的源码,对指定方法进行逐行注释分析。
📌 如何找到 ArrayList 源码?
- 在 JDK 安装目录下找到
src.zip,解压后进入java.base/java/util/ArrayList.java- 或者使用 IDE(IntelliJ IDEA / Eclipse)直接打开 ArrayList 类,点击「Download Sources」
- 或者查阅 OpenJDK 在线源码:ArrayList.java (JDK 21)
包名:p1_arraylist_source
知识点:ArrayList 构造方法、默认容量、空数组与默认空数组的区别
找到 ArrayList.java 中的以下三个构造方法,为每一行代码添加详细的中文注释(包括大括号和条件语句)。
要求:
请复制以下三个构造方法到你的 Java 源文件中,并在每一行上方/后方添加注释:
// ==================== 构造方法 1:指定初始容量 ====================
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
}
}
// ==================== 构造方法 2:无参构造 ====================
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// ==================== 构造方法 3:传入集合 ====================
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
elementData = EMPTY_ELEMENTDATA;
}
}
EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 都是空数组,为什么要有两个?有什么区别?elementData 不直接 new Object[10],而是先指向一个空数组?c.getClass() == ArrayList.class?如果不判断直接使用 Arrays.copyOf(a, size, Object[].class) 会有什么问题?Collection<? extends E> c 中的 ? extends E 是什么意思?有什么用?包名:p1_arraylist_source(与第 1 题同一个包)
知识点:ArrayList 扩容机制、容量缩减、自动扩容、位运算
为以下三个与容量 / 扩容相关的方法逐行添加中文注释。
// ==================== trimToSize() - 缩减容量 ====================
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
// ==================== ensureCapacity(int) - 确保容量 ====================
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;
grow(minCapacity);
}
}
// ==================== grow(int) - 扩容核心方法 ====================
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity,
oldCapacity >> 1);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
modCount 是什么? 它在 ArrayList 中起什么作用?(提示:fail-fast 机制)ArraysSupport.newLength(int oldLength, int minGrowth, int prefGrowth) 这个方法是干什么的?三个参数分别是什么含义?它的返回值是如何计算的?oldCapacity >> 1 计算结果是多少?为什么扩容时选择"旧容量的一半"作为 preferred growth?ensureCapacity 方法中的两个条件为什么要用 && 连接? 什么情况下即使 minCapacity > elementData.length 也不会立即扩容?trimToSize() 中的三目运算符 (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size) 分别对应什么场景?grow() 方法中的 if-else 分支:什么情况下进入 if 分支?什么情况下进入 else 分支?包名:p1_arraylist_source(与第 1 题同一个包)
知识点:ArrayList 添加元素、删除元素、清空集合、数组拷贝
为以下四个与元素操作相关的方法逐行添加中文注释。
// ==================== add(E, Object[], int) - 内部添加辅助方法 ====================
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
// ==================== remove(int) - 按索引删除元素 ====================
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked")
E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
// ==================== fastRemove(Object[], int) - 快速删除(不检查边界) ====================
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
// ==================== clear() - 清空集合 ====================
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
add(E e) 的返回类型是 boolean,但始终返回 true,为什么还要设计成有返回值?add(E e, Object[] elementData, int s) 为什么被设计成 private 方法? 为什么要把参数 elementData 和 s 传进来,而不是直接在方法内访问成员变量?Objects.checkIndex(index, size) 是做什么的?如果 index 越界会怎样?@SuppressWarnings("unchecked") 是什么作用?为什么这里需要加这个注解?fastRemove 方法中的 System.arraycopy 是做什么的?参数 es, i + 1, es, i, newSize - i 分别代表什么?fastRemove 删除元素后,为什么要将最后一个位置设为 null?(即 es[size = newSize] = null;)clear() 方法的 for 循环中,为什么 i < to 的条件不直接写 i < size?为什么要先把 size 赋值给 to,然后设置 size = 0?clear() 方法遍历置 null 和直接 elementData = EMPTY_ELEMENTDATA 有什么区别? 哪种方式更好?为什么 JDK 选择了前者?包名:p2_myarraylist
知识点:综合运用泛型、数组操作、扩容机制
基于你对 ArrayList 源码的理解,手写一个简化版的 MyArrayList<E> 类,实现以下方法:
| 方法 | 说明 |
|---|---|
MyArrayList() |
无参构造,默认容量 10 |
MyArrayList(int initialCapacity) |
指定初始容量 |
boolean add(E e) |
添加元素(自动扩容) |
E get(int index) |
获取元素(检查越界) |
E set(int index, E element) |
修改元素 |
E remove(int index) |
删除元素(返回被删除的元素) |
int size() |
返回元素个数 |
boolean isEmpty() |
判断是否为空 |
void clear() |
清空所有元素 |
String toString() |
返回格式如 [元素1, 元素2, ...] |
要求:
MyArrayList<E>)Object[] 存储数据MyArrayListTest 测试所有方法测试数据示例:
MyArrayList<String> list = new MyArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
System.out.println(list); // [Java, Python, C++]
System.out.println(list.size()); // 3
System.out.println(list.get(1)); // Python
list.set(1, "JavaScript");
list.remove(0);
System.out.println(list); // [JavaScript, C++]
System.out.println(list.isEmpty()); // false
list.clear();
System.out.println(list.isEmpty()); // true
| 题号 | 分值 | 评分维度 |
|---|---|---|
| 第 1 题 | 25 分 | 注释是否覆盖每一行、注释是否准确、思考题是否回答正确 |
| 第 2 题 | 30 分 | 注释深度(是否有查阅资料)、扩容机制理解是否正确、思考题回答质量 |
| 第 3 题 | 30 分 | 增删改方法注释是否完整、System.arraycopy 理解是否正确、思考题回答质量 |
| 第 4 题 | 15 分(加分) | 功能完整性、代码质量、测试覆盖度 |
ArrayList.java(位于 JDK 安装目录的 src.zip 中)