1. Java学习日记——DAY22:深入理解集合框架与泛型编程
作为一名从机械转行Java的开发者,我在学习第22天时遇到了集合框架这个重要关卡。记得那天在图书馆从早泡到晚,笔记本上画满了各种集合类的关系图。本文将还原那天的学习轨迹,重点拆解ArrayList与LinkedList的核心差异,以及如何用泛型写出更安全的代码。
1.1 为什么集合框架是Java核心基础
Java集合框架(Java Collections Framework)位于java.util包中,它就像程序员的数据收纳箱。与数组相比,集合的三大优势让我印象深刻:
- 动态扩容:不用预先声明大小
- 丰富API:内置排序、查找等方法
- 类型安全:通过泛型避免强制类型转换
初学时最常踩的坑是忘记集合只能存储对象。当我尝试List<int> list = new ArrayList<>()时,IDE报错的红色波浪线让我第一次理解了包装类的必要性。
2. List接口的两种经典实现对比
2.1 ArrayList底层原理剖析
通过IDEA的Debug工具跟踪源码,发现ArrayList内部维护着Object[] elementData数组。它的扩容机制特别有意思:
java复制// JDK1.8中的grow方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
关键经验:初始化时若能预估数据量,应使用
new ArrayList<>(initialCapacity)避免频繁扩容开销
2.2 LinkedList的链式结构奥秘
与ArrayList不同,LinkedList的Node节点让我联想到火车车厢:
java复制private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
// 构造方法省略...
}
通过绘制内存结构图,我理解了它的四大特性:
- 插入删除O(1)时间复杂度
- 随机访问需要遍历,效率O(n)
- 实现了Deque接口,可作双端队列使用
- 每个元素多消耗24字节(12字节对象头+前后指针)
3. 泛型编程的实战技巧
3.1 类型擦除带来的限制
在实现合并数组的方法时,我遇到了泛型数组创建的难题:
java复制// 编译报错:Cannot create a generic array of T
public static <T> T[] mergeArrays(T[] arr1, T[] arr2) {
T[] result = new T[arr1.length + arr2.length]; // 错误写法
// 正确写法应使用Array.newInstance
@SuppressWarnings("unchecked")
T[] result = (T[]) Array.newInstance(
arr1.getClass().getComponentType(),
arr1.length + arr2.length
);
System.arraycopy(arr1, 0, result, 0, arr1.length);
System.arraycopy(arr2, 0, result, arr1.length, arr2.length);
return result;
}
3.2 通配符的三种境界
理解PECS原则(Producer Extends, Consumer Super)花了整整两小时:
<? extends T>:只能读取,不能写入(除null)<? super T>:只能写入,读取需强转<?>:无限定通配符,等同于<? extends Object>
这个知识点在实现Collections.copy()方法时突然开窍:
java复制public static <T> void copy(
List<? super T> dest, List<? extends T> src) {
// src是生产者(读取),dest是消费者(写入)
for (int i=0; i<src.size(); i++)
dest.set(i, src.get(i));
}
4. 性能优化与避坑指南
4.1 遍历操作的性能陷阱
用JMH做微基准测试时,发现不同遍历方式性能差异显著:
| 遍历方式 | ArrayList(ms) | LinkedList(ms) |
|---|---|---|
| for循环get() | 15 | 3842 |
| 迭代器Iterator | 18 | 20 |
| forEach循环 | 19 | 22 |
血泪教训:永远不要用get()遍历LinkedList!曾因此导致生产环境接口超时
4.2 线程安全问题的解决方案
在电商项目模拟抢购时,直接使用ArrayList导致库存超卖。最终采用三种解决方案对比:
Collections.synchronizedList():简单但性能差CopyOnWriteArrayList:适合读多写少场景- 改用
ConcurrentLinkedQueue:最终选择的方案
5. 项目实战:实现自定义LRU缓存
结合当天所学,我实现了一个简化版LRU缓存:
java复制public class LRUCache<K,V> extends LinkedHashMap<K,V> {
private final int maxCapacity;
public LRUCache(int maxCapacity) {
super(maxCapacity, 0.75f, true);
this.maxCapacity = maxCapacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > maxCapacity;
}
// 线程安全增强
public synchronized V putIfAbsent(K key, V value) {
if (!containsKey(key)) {
put(key, value);
return null;
}
return get(key);
}
}
这个案例让我深刻体会到集合框架的设计精妙——通过继承LinkedHashMap并重写removeEldestEntry方法,仅用20行代码就实现了LRU淘汰策略。后来在准备面试时,发现这竟然是美团的高频面试题。
