集合设计原则:框架背后的设计哲学
接口与实现分离
Java 集合框架最核心的原则:用接口声明变量,用实现类创建对象。
java
// ✅ 面向接口编程,灵活切换实现
List<String> list = new ArrayList<>();
// 哪天要改成 LinkedList?只需改这一行
List<String> list = new LinkedList<>();
// ❌ 直接用实现类声明,失去了灵活性
ArrayList<String> list = new ArrayList<>();这个原则的威力在于:当需求变化时,你只需要改一行代码。
记住这个原则
变量类型用接口(List、Set、Map),创建对象用具体实现(ArrayList、HashSet、HashMap)。
为什么 HashMap 比 Hashtable 更推荐
java
// ❌ 遗留代码风格
Hashtable<String, Integer> old = new Hashtable<>();
// ✅ 现代代码风格
Map<String, Integer> modern = new HashMap<>();两者功能几乎一样,但 HashMap 变量类型是 Map 接口,可以随时替换成 TreeMap(需要排序)或 ConcurrentHashMap(需要并发),而 Hashtable 变量类型是具体类,改起来要大改。
泛型:编译期的安全网
没有泛型的时代是什么样子?
java
// JDK 1.4:无泛型,到处都要强制类型转换
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换
// JDK 5+:泛型,类型安全
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 无需转换,编译时就检查泛型的设计哲学:错误在编译期发现,而不是在运行时。一个 ClassCastException 可能让你的程序在凌晨 3 点崩溃,而泛型让这些错误在 javac 阶段就被揪出来。
java
// 编译错误,而不是运行时崩溃
List<String> list = new ArrayList<>();
list.add(123); // 编译不通过:类型不兼容可选操作:接口的边界
有些操作在某些实现中不可行,Java 用「可选操作」模式来处理这个问题:
java
public interface List<E> {
// 所有实现都要支持的方法
int size();
boolean isEmpty();
// 可选操作(UnsupportedOperationException)
default void sort(Comparator<? super E> c) {
throw new UnsupportedOperationException();
}
}Arrays.asList() 返回的列表不支持增删操作:
java
List<String> fixed = Arrays.asList("a", "b", "c");
fixed.add("d"); // UnsupportedOperationException
fixed.remove(0); // UnsupportedOperationException
fixed.set(0, "x"); // 这个可以!这就是「固定大小列表」和「可变列表」的区别——Arrays.asList() 返回的底层就是原数组,不是真正独立的列表。
视图与快照
集合框架中有两种数据提供方式:视图(view)和快照(snapshot)。
java
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
// 视图:底层共享,修改会影响原 Map
Set<String> keys = map.keySet();
keys.remove("a"); // map 里 "a" 也被删了!
System.out.println(map); // {b=2}
// 快照:独立副本
Set<String> snapshot = new HashSet<>(map.keySet());
snapshot.remove("b"); // 不影响 map
System.out.println(map); // {b=2}keySet()、values()、entrySet() 返回的都是视图,不是副本。
面试常问的坑
不要在遍历 Map 的同时修改它(ConcurrentModificationException),但可以通过迭代器的 remove() 来安全删除:
java
Iterator<String> it = map.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.equals("b")) {
it.remove(); // ✅ 安全删除
}
}算法复用:Collections 工具类
Collections 和 Arrays 提供了一套通用的算法,让所有集合都能复用:
java
import java.util.*;
public class AlgorithmReuseDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
// 排序
Collections.sort(numbers);
System.out.println("排序后: " + numbers);
// 反转
Collections.reverse(numbers);
System.out.println("反转后: " + numbers);
// 洗牌
Collections.shuffle(numbers);
System.out.println("洗牌后: " + numbers);
// 二分查找(必须先排序)
int index = Collections.binarySearch(numbers, 5);
System.out.println("5 的位置: " + index);
// 不可变视图
List<Integer> unmodifiable = Collections.unmodifiableList(numbers);
// unmodifiable.add(10); // UnsupportedOperationException
}
}JDK 8+ 的 Stream 也能复用
java
import java.util.*;
import java.util.stream.*;
public class StreamAlgorithmDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
// 过滤 + 映射 + 收集
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.sorted()
.collect(Collectors.toList());
System.out.println("偶数排序: " + evens);
// 统计
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("统计: " + stats.getMax() + ", " + stats.getMin() + ", " + stats.getAverage());
}
}线程安全的演进
Java 集合的线程安全经历了三次重大演进:
java
// 1. 遗留 synchronized(低效,全局锁)
Map<String, Integer> v1 = new Hashtable<>();
Map<String, Integer> v2 = Collections.synchronizedMap(new HashMap<>());
// 2. 并发包(高效,粒度锁 / CAS)
Map<String, Integer> v3 = new ConcurrentHashMap<>();
// 3. 读多写少(Copy-On-Write)
List<String> v4 = new CopyOnWriteArrayList<>();
Set<String> v5 = new CopyOnWriteArraySet<>();性能对比
java
import java.util.*;
import java.util.concurrent.*;
public class ConcurrencyPerformanceDemo {
public static void main(String[] args) throws InterruptedException {
int threads = 100;
int ops = 100000;
// synchronizedMap:全局锁
Map<Integer, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
long syncTime = benchmark(syncMap, threads, ops);
// ConcurrentHashMap:分段锁/CAS
Map<Integer, Integer> concurrentMap = new ConcurrentHashMap<>();
long concurrentTime = benchmark(concurrentMap, threads, ops);
System.out.println("synchronizedMap: " + syncTime + " ms");
System.out.println("ConcurrentHashMap: " + concurrentTime + " ms");
System.out.println("提升: " + (double) syncTime / concurrentTime + "x");
}
static long benchmark(Map<Integer, Integer> map, int threads, int ops)
throws InterruptedException {
Thread[] ts = new Thread[threads];
long start = System.nanoTime();
for (int i = 0; i < threads; i++) {
final int base = i * ops;
ts[i] = new Thread(() -> {
for (int j = 0; j < ops; j++) {
map.put(base + j, j);
}
});
ts[i].start();
}
for (Thread t : ts) t.join();
return (System.nanoTime() - start) / 1_000_000;
}
}典型结果:ConcurrentHashMap 比 synchronizedMap 快 5-10 倍。
