1. 集合框架的继承关系解析
Java集合框架中List、Set、Map三者的继承关系,是每个Java开发者必须掌握的基础知识。让我们先看一个典型的面试场景:
java复制Collection<String> collection1 = new ArrayList<>(); // 编译通过
Collection<String> collection2 = new HashSet<>(); // 编译通过
Collection<String> collection3 = new HashMap<>(); // 编译报错
为什么前两个能编译通过而第三个会报错?这就要从Java集合框架的设计说起。Collection是Java集合框架的根接口之一,但并不是所有集合类型都继承自它。
1.1 Collection接口的直系后代
打开JDK源码可以看到:
java复制public interface List<E> extends Collection<E> {...}
public interface Set<E> extends Collection<E> {...}
List和Set确实都直接继承自Collection接口,这也是为什么它们能共享许多共性方法:
size()isEmpty()contains()iterator()toArray()- ...
1.2 Map的独立地位
Map的接口声明则是:
java复制public interface Map<K,V> {...}
Map自成体系,不继承Collection。这是因为Map存储的是键值对(key-value pairs),与Collection的单个元素存储方式有本质区别。但Map提供了三个视图方法:
keySet():返回键的Set视图values():返回值的Collection视图entrySet():返回键值对的Set视图
2. 三大集合类型的核心区别
2.1 元素存储方式对比
| 特性 | List | Set | Map |
|---|---|---|---|
| 元素重复性 | 允许重复 | 不允许重复 | 键不可重复,值可重复 |
| 元素顺序 | 有序 | 无序(HashSet) | 无序(HashMap) |
| 访问方式 | 索引访问 | 迭代器访问 | 键访问 |
| 空元素 | 允许多个null | 允许一个null | 键值都可为null |
2.2 常见实现类分析
List实现类:
- ArrayList:基于动态数组,随机访问快
- LinkedList:基于链表,插入删除快
- Vector:线程安全版ArrayList
Set实现类:
- HashSet:基于哈希表,最快查询
- LinkedHashSet:保持插入顺序
- TreeSet:基于红黑树,自动排序
Map实现类:
- HashMap:最常用,基于哈希表
- LinkedHashMap:保持插入顺序
- TreeMap:基于红黑树,按键排序
- ConcurrentHashMap:线程安全版HashMap
3. 使用场景与性能考量
3.1 何时选择List
适合场景:
- 需要保留元素插入顺序
- 需要频繁按索引访问元素
- 允许重复元素存在
性能陷阱:
java复制// 错误示范:在ArrayList中间频繁插入
List<Integer> list = new ArrayList<>();
for(int i=0; i<100000; i++){
list.add(0, i); // 每次插入都导致数组拷贝
}
// 正确做法:改用LinkedList
List<Integer> list = new LinkedList<>();
3.2 何时选择Set
适合场景:
- 需要元素唯一性保证
- 需要快速判断元素是否存在
- 不关心元素顺序
重要技巧:
java复制// 利用Set去重
List<String> names = Arrays.asList("Tom", "Jerry", "Tom");
Set<String> uniqueNames = new HashSet<>(names);
3.3 何时选择Map
适合场景:
- 需要键值对存储
- 需要通过键快速查找值
- 需要建立对象间映射关系
性能优化:
java复制// 初始化时设置容量避免扩容
Map<String, Integer> map = new HashMap<>(1000);
// 使用computeIfAbsent优化代码
map.computeIfAbsent("key", k -> calculateValue(k));
4. 集合间的转换技巧
4.1 List与Set互转
java复制// List转Set(自动去重)
List<String> list = Arrays.asList("A", "B", "A");
Set<String> set = new HashSet<>(list);
// Set转List
List<String> newList = new ArrayList<>(set);
4.2 Map与List/Set转换
java复制Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
// 获取键集合
Set<String> keys = map.keySet();
// 获取值集合
Collection<Integer> values = map.values();
// 键值对转List
List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
5. 常见问题排查
5.1 集合遍历时的修改异常
java复制Set<String> set = new HashSet<>(Arrays.asList("A", "B", "C"));
for(String s : set){
if(s.equals("B")){
set.remove(s); // 抛出ConcurrentModificationException
}
}
// 正确做法:使用迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()){
if(it.next().equals("B")){
it.remove(); // 安全删除
}
}
5.2 自定义对象的集合问题
java复制class Person {
String name;
// 省略equals和hashCode方法
}
Set<Person> set = new HashSet<>();
set.add(new Person("Tom"));
set.contains(new Person("Tom")); // 可能返回false
// 解决方案:重写equals和hashCode方法
@Override
public boolean equals(Object o) {...}
@Override
public int hashCode() {...}
5.3 线程安全问题
java复制// 非线程安全示例
List<Integer> list = new ArrayList<>();
// 多线程同时调用list.add()会导致数据错乱
// 解决方案1:使用Collections工具类
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
// 解决方案2:使用并发集合
List<Integer> copyOnWriteList = new CopyOnWriteArrayList<>();
6. 高级特性与最佳实践
6.1 Java 8的Stream操作
java复制List<String> names = Arrays.asList("Tom", "Jerry", "Alice");
// 过滤转换
List<String> filtered = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
// 统计计算
long count = names.stream().distinct().count();
6.2 不可变集合
java复制// Java 9+ 创建不可变集合
List<String> immutableList = List.of("A", "B", "C");
Set<String> immutableSet = Set.of("A", "B");
Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);
// 修改操作会抛出UnsupportedOperationException
immutableList.add("D"); // 抛出异常
6.3 集合初始化技巧
java复制// 双括号初始化(匿名内部类方式)
List<String> list = new ArrayList<>() {{
add("A");
add("B");
}};
// Java 9+ 工厂方法
List<String> list = List.of("A", "B");
Set<String> set = Set.of("A", "B");
Map<String, Integer> map = Map.of("A", 1, "B", 2);
7. 性能优化实战
7.1 选择合适的集合类型
java复制// 场景1:频繁随机访问
List<String> arrayList = new ArrayList<>(); // O(1)访问
// 场景2:频繁插入删除
List<String> linkedList = new LinkedList<>(); // O(1)插入删除
// 场景3:需要去重
Set<String> hashSet = new HashSet<>(); // O(1)查找
7.2 预分配集合大小
java复制// 已知最终大小的List
List<String> list = new ArrayList<>(1000);
// 已知元素数量的HashMap(负载因子0.75)
Map<String, Integer> map = new HashMap<>(expectedSize * 4 / 3 + 1);
7.3 避免装箱拆箱
java复制// 使用原始类型专用集合
IntList intList = new IntArrayList(); // eclipse-collections
intList.add(1);
int value = intList.get(0); // 无装箱开销
8. 设计模式中的集合应用
8.1 组合模式示例
java复制interface Component {
void operation();
}
class Composite implements Component {
private List<Component> children = new ArrayList<>();
public void add(Component c) {
children.add(c);
}
@Override
public void operation() {
children.forEach(Component::operation);
}
}
8.2 观察者模式示例
java复制class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) {
observers.add(o);
}
public void notifyObservers() {
observers.forEach(Observer::update);
}
}
9. 集合框架的演进
9.1 Java 5泛型引入
java复制// 泛型前(需要强制转换)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
// 泛型后(类型安全)
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动类型推断
9.2 Java 8 Stream API
java复制List<String> names = Arrays.asList("Tom", "Jerry");
long count = names.stream()
.filter(n -> n.startsWith("T"))
.count();
9.3 Java 9不可变集合工厂
java复制List<String> list = List.of("a", "b");
Set<String> set = Set.of("a", "b");
Map<String, Integer> map = Map.of("a", 1, "b", 2);
10. 面试常见问题解析
10.1 HashMap工作原理
HashMap基于哈希表实现,通过hashCode()计算存储位置。JDK 8后,当链表长度超过8时会转为红黑树,提升查询效率。
10.2 ArrayList与LinkedList区别
- ArrayList基于动态数组,随机访问快(O(1)),插入删除慢(O(n))
- LinkedList基于双向链表,插入删除快(O(1)),随机访问慢(O(n))
10.3 ConcurrentHashMap实现原理
JDK 8的ConcurrentHashMap采用CAS+synchronized实现线程安全,将数据分为多个段(segment),减少锁竞争。
11. 实际开发中的经验之谈
11.1 集合选择黄金法则
- 需要键值对?选Map
- 需要元素唯一?选Set
- 需要保留顺序?选List
- 线程安全需求?考虑并发集合
11.2 性能优化要点
- 预估集合大小,初始化时设置合适容量
- 遍历大集合优先使用迭代器
- 多读少写场景考虑CopyOnWriteArrayList
- 避免在循环中直接操作集合
11.3 代码可读性技巧
java复制// 使用Guava创建集合更直观
List<String> list = Lists.newArrayList("a", "b");
Map<String, Integer> map = Maps.newHashMapWithExpectedSize(10);
// 使用Collections工具类简化操作
Collections.sort(list);
Collections.reverse(list);
