跳到主要内容

Java集合框架详解

集合框架总览

什么是Java集合框架

Java集合框架是Java开发中最常用的工具之一,它为我们提供了一套完整的数据结构和算法实现。简单来说,集合就是一个容器,用来存储和管理一组对象。

Java集合框架主要包含两大类型:

  • Collection接口: 用于存储单个元素的集合
  • Map接口: 用于存储键值对(key-value)的集合

为什么需要使用集合

在实际开发中,我们经常需要存储一组对象,虽然数组也能完成这个任务,但数组存在诸多局限:

  1. 长度固定: 数组创建后长度不可变,无法动态扩展
  2. 类型单一: 数组只能存储同一类型的元素
  3. 操作复杂: 增删元素需要手动移动其他元素
  4. 功能有限: 缺少高级操作如排序、查找等内置方法

相比之下,Java集合框架提供了:

  • 动态扩容能力
  • 丰富的API方法
  • 多样化的数据结构
  • 泛型支持保证类型安全
  • 内置算法提高开发效率

集合框架的层次结构

Java集合框架的核心接口主要分为以下几类:

四大接口的特点对比

接口有序性可重复性主要特点使用场景
List有序可重复按索引存储,维护插入顺序需要保持元素顺序的场景
Set无序不可重复自动去重,不保证顺序需要元素唯一性的场景
Queue有序可重复按特定规则排队任务调度、消息队列
MapKey无序Key不可重复键值对映射需要快速查找的场景

List接口详解

List的底层实现

List接口的主要实现类及其底层数据结构:

底层结构说明:

  • ArrayList: 基于动态数组,查询快,增删慢
  • LinkedList: 基于双向链表,增删快,查询慢
  • Vector: 类似ArrayList但线程安全,性能较低(已过时)

ArrayList vs 数组

ArrayList相比传统数组的优势:

数组示例:

// 创建固定大小的数组
String[] courses = new String[]{"数学", "语文", "英语"};

// 修改元素
courses[0] = "物理";
System.out.println(Arrays.toString(courses)); // [物理, 语文, 英语]

// 删除元素需要手动移位
for (int i = 0; i < courses.length - 1; i++) {
courses[i] = courses[i + 1];
}
courses[courses.length - 1] = null;
System.out.println(Arrays.toString(courses)); // [语文, 英语, null]

ArrayList示例:

// 创建动态列表
ArrayList<String> courses = new ArrayList<>(Arrays.asList("数学", "语文", "英语"));

// 添加元素
courses.add("物理");
System.out.println(courses); // [数学, 语文, 英语, 物理]

// 修改元素
courses.set(0, "化学");
System.out.println(courses); // [化学, 语文, 英语, 物理]

// 删除元素
courses.remove(0);
System.out.println(courses); // [语文, 英语, 物理]

主要区别:

特性数组ArrayList
长度固定动态扩容
泛型支持不支持支持
基本类型直接存储需要包装类
API方法丰富
创建方式必须指定大小可选大小

ArrayList vs Vector

ArrayList:

  • 非线程安全,性能更高
  • 是List的主要实现类
  • 适用于单线程环境

Vector:

  • 线程安全,使用synchronized同步
  • JDK 1.0引入的古老类
  • 性能较差,已被淘汰

现代替代方案:

// 推荐使用并发集合
List<String> safeList = new CopyOnWriteArrayList<>();

// 或者手动同步
List<String> syncList = Collections.synchronizedList(new ArrayList<>());

Vector vs Stack

  • Vector: 可变长数组,线程安全的List
  • Stack: 继承自Vector,实现后进先出(LIFO)栈

两者都使用synchronized实现线程安全,但现在都已过时,推荐使用:

  • Deque接口: 替代Stack
  • ConcurrentHashMap: 替代线程安全需求

ArrayList的null值处理

ArrayList可以存储null值,但不推荐:

ArrayList<String> subjects = new ArrayList<>();
subjects.add(null);
subjects.add("计算机网络");
System.out.println(subjects); // [null, 计算机网络]

不推荐的原因:

  1. null值没有实际意义
  2. 容易导致空指针异常
  3. 使代码难以维护和理解

ArrayList的时间复杂度分析

插入操作:

详细说明:

  1. 头部插入: O(n)

    • 所有元素需要向后移动
    • 最坏情况
  2. 尾部插入: O(1)或O(n)

    • 容量充足时: O(1)
    • 需要扩容时: O(n)
  3. 指定位置插入: O(n)

    • 平均移动n/2个元素

删除操作:

  1. 头部删除: O(n) - 所有元素前移
  2. 尾部删除: O(1) - 直接删除
  3. 指定位置删除: O(n) - 平均移动n/2个元素

图示例子:

// 底层数组容量为10,当前存储5个元素
// 索引: 0 1 2 3 4 5 6 7 8 9
// 数据: [10, 20, 30, 40, 50, , , , , ]

// 在索引2插入元素60
// 索引: 0 1 2 3 4 5 6 7 8 9
// 数据: [10, 20, 60, 30, 40, 50, , , , ]

// 删除索引2的元素
// 索引: 0 1 2 3 4 5 6 7 8 9
// 数据: [10, 20, 30, 40, 50, , , , , ]

LinkedList的时间复杂度分析

LinkedList基于双向链表实现:

时间复杂度:

操作时间复杂度说明
头部插入/删除O(1)只需修改头指针
尾部插入/删除O(1)只需修改尾指针
指定位置插入/删除O(n)需要遍历定位
随机访问O(n)需要遍历链表

删除节点示意:

LinkedList为什么不实现RandomAccess接口

RandomAccess接口是一个标记接口,表示支持快速随机访问:

public interface RandomAccess {
// 空接口,仅作标记
}

原因分析:

Collections工具类的应用:

public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {
// 判断是否支持随机访问
if (list instanceof RandomAccess || list.size() < BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key); // 索引访问
else
return Collections.iteratorBinarySearch(list, key); // 迭代器访问
}

ArrayList vs LinkedList全面对比

核心差异:

详细对比表:

特性ArrayListLinkedList
底层结构Object[]数组双向链表
随机访问支持,O(1)不支持,O(n)
头部插入O(n)O(1)
尾部插入O(1)O(1)
中间插入O(n)O(n)
内存占用预留容量空间每个节点额外存储两个指针
RandomAccess实现未实现
适用场景查询多,改动少头尾操作频繁

实际使用建议:

在99%的场景下,优先使用ArrayList。即使是频繁增删的场景,ArrayList的性能通常也不会比LinkedList差。连LinkedList的作者Joshua Bloch都表示自己从不使用LinkedList。

原因:

  1. ArrayList的局部性更好,CPU缓存友好
  2. LinkedList每次增删都要创建对象,GC压力大
  3. LinkedList只在头尾操作时才有O(1)优势

ArrayList扩容机制

ArrayList的扩容机制是面试高频考点:

扩容步骤:

  1. 检查容量是否充足
  2. 不足时扩容为原来的1.5倍
  3. 创建新数组
  4. 复制原数组数据
  5. 添加新元素

扩容细节分析:

源码解析:

// ArrayList的扩容核心方法
private void grow(int minCapacity) {
// 获取旧容量
int oldCapacity = elementData.length;

// 计算新容量:旧容量的1.5倍
// oldCapacity >> 1 等价于 oldCapacity / 2
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);
}

关键点说明:

特性说明
扩容倍数1.5倍(右移1位相当于除以2)
最大容量Integer.MAX_VALUE - 8 (约21亿)
扩容时机添加元素时容量不足
时间复杂度O(n),需要复制所有元素
空间浪费可能预留较多空闲空间

Vector的扩容对比:

// Vector扩容为原来的2倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);

ArrayList与Vector的扩容对比

为什么ArrayList扩容是1.5倍?

  1. 节省内存: 相比2倍扩容,1.5倍更节省空间
  2. 黄金分割: 1.5接近黄金分割比例,能够充分利用之前释放的内存
  3. 性能平衡: 既避免频繁扩容,又不会浪费太多内存

使用LinkedList实现LRU缓存

LRU(Least Recently Used,最近最少使用)是一种常见的缓存淘汰策略。我们可以用LinkedList实现一个简单的LRU缓存:

实现原理:

  • 最近访问的元素放在链表头部
  • 链表尾部是最久未使用的元素
  • 缓存满时淘汰尾部元素

代码实现:

public class SimpleLRUCache<T> {
private final int capacity; // 缓存容量
private final LinkedList<T> cache = new LinkedList<>();

public SimpleLRUCache(int capacity) {
this.capacity = capacity;
}

/**
* 添加元素到缓存
*/
public void put(T element) {
// 如果元素已存在,先移除
cache.remove(element);

// 检查容量
if (cache.size() >= capacity) {
// 移除最久未使用的元素(尾部)
T removed = cache.removeLast();
System.out.println("缓存已满,淘汰: " + removed);
}

// 将新元素添加到头部
cache.addFirst(element);
}

/**
* 访问缓存中的元素
*/
public T get(int index) {
if (index < 0 || index >= cache.size()) {
return null;
}

// 获取元素
T element = cache.get(index);

// 将访问的元素移到头部(表示最近使用)
cache.remove(element);
cache.addFirst(element);

return element;
}

/**
* 获取缓存当前状态
*/
public LinkedList<T> getCache() {
return new LinkedList<>(cache);
}

@Override
public String toString() {
return "LRU缓存: " + cache.toString();
}
}

// 使用示例
public class LRUTest {
public static void main(String[] args) {
SimpleLRUCache<String> cache = new SimpleLRUCache<>(3);

// 添加元素
cache.put("页面A");
cache.put("页面B");
cache.put("页面C");
System.out.println(cache); // [页面C, 页面B, 页面A]

// 访问页面A,A移到最前面
cache.get(2);
System.out.println(cache); // [页面A, 页面C, 页面B]

// 添加新页面D,淘汰最久未用的页面B
cache.put("页面D");
System.out.println(cache); // [页面D, 页面A, 页面C]
}
}

输出结果:

LRU缓存: [页面C, 页面B, 页面A]
LRU缓存: [页面A, 页面C, 页面B]
缓存已满,淘汰: 页面B
LRU缓存: [页面D, 页面A, 页面C]

LRU缓存的应用场景:

  • 浏览器页面缓存
  • 数据库查询结果缓存
  • Redis等缓存系统
  • 操作系统页面置换算法

ArrayList的subList陷阱

ArrayList的subList方法在使用时需要特别注意,它返回的是原List的视图,而不是一个新的独立List。

subList的底层原理:

public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
// 返回SubList内部类实例
return new SubList(this, 0, fromIndex, toIndex);
}

// SubList是ArrayList的内部类
private class SubList extends AbstractList<E> {
private final AbstractList<E> parent; // 指向原List
private final int parentOffset;
private final int offset;
int size;

SubList(AbstractList<E> parent, int offset,
int fromIndex, int toIndex) {
this.parent = parent; // 保存原List引用
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
}

使用陷阱示例:

陷阱1: 类型转换错误

List<String> books = new ArrayList<>();
books.add("Java编程思想");
books.add("Effective Java");
books.add("深入理解JVM");

// 错误:不能强转为ArrayList
try {
ArrayList<String> subBooks = (ArrayList<String>) books.subList(0, 2);
} catch (ClassCastException e) {
System.out.println("错误: subList返回的是SubList,不是ArrayList!");
}

// 正确:使用List接口接收
List<String> subBooks = books.subList(0, 2);
System.out.println("子列表: " + subBooks);

陷阱2: 非结构性修改的相互影响

List<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("Go");
languages.add("C++");
languages.add("JavaScript");

// 获取子列表[索引1-3)
List<String> subLang = languages.subList(1, 4);
System.out.println("原列表: " + languages);
System.out.println("子列表: " + subLang);

// 修改子列表的元素
subLang.set(1, "Rust");

System.out.println("\n修改子列表后:");
System.out.println("原列表: " + languages); // [Java, Python, Rust, C++, JavaScript]
System.out.println("子列表: " + subLang); // [Python, Rust, C++]

输出:

原列表: [Java, Python, Go, C++, JavaScript]
子列表: [Python, Go, C++]

修改子列表后:
原列表: [Java, Python, Rust, C++, JavaScript]
子列表: [Python, Rust, C++]

陷阱3: 结构性修改子列表影响原列表

List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 6; i++) {
numbers.add(i * 10);
}

List<Integer> subNumbers = numbers.subList(2, 5);
System.out.println("原列表: " + numbers); // [10, 20, 30, 40, 50, 60]
System.out.println("子列表: " + subNumbers); // [30, 40, 50]

// 在子列表中添加元素
subNumbers.add(999);

System.out.println("\n向子列表添加元素后:");
System.out.println("原列表: " + numbers); // [10, 20, 30, 40, 50, 999, 60]
System.out.println("子列表: " + subNumbers); // [30, 40, 50, 999]

陷阱4: 结构性修改原列表导致异常

List<String> cities = new ArrayList<>();
cities.add("北京");
cities.add("上海");
cities.add("广州");
cities.add("深圳");
cities.add("杭州");

List<String> subCities = cities.subList(1, 4);
System.out.println("原列表: " + cities);
System.out.println("子列表: " + subCities);

// 修改原列表的结构
cities.add("成都");

try {
// 尝试访问子列表会抛出异常
System.out.println("子列表: " + subCities);
} catch (ConcurrentModificationException e) {
System.out.println("错误: 原列表结构改变后,子列表失效!");
}

三种修改的总结:

如何安全使用subList:

// 方法1: 创建独立的副本
List<String> original = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
List<String> independent = new ArrayList<>(original.subList(1, 3));

// 方法2: 使用Stream API
List<String> streamCopy = original.stream()
.skip(1)
.limit(2)
.collect(Collectors.toList());

// 方法3: 使用Guava库
List<String> guavaCopy = Lists.newArrayList(original.subList(1, 3));

// 修改副本不影响原列表
independent.add("E");
System.out.println("原列表: " + original); // [A, B, C, D]
System.out.println("独立副本: " + independent); // [B, C, E]

最佳实践建议:

使用场景推荐方法
只读访问子列表直接使用subList
需要修改子列表创建独立副本
原列表可能变化避免使用subList
需要类型转换使用List接口,不要强转

集合的fail-fast和fail-safe机制

fail-fast(快速失败):

设计思想: 一旦检测到可能发生错误,立即抛出异常,停止执行。

ArrayList的fail-fast示例:

List<Integer> scores = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(2);

for (int i = 1; i <= 5; i++) {
scores.add(i * 10);
}
System.out.println("初始成绩列表: " + scores);

// 线程1: 遍历列表
Thread reader = new Thread(() -> {
try {
for (Integer score : scores) {
System.out.println("读取线程看到成绩: " + score);
Thread.sleep(100);
}
} catch (ConcurrentModificationException e) {
System.err.println("!!! 检测到并发修改,快速失败!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});

// 线程2: 修改列表
Thread writer = new Thread(() -> {
try {
Thread.sleep(50);
System.out.println("-> 修改线程删除成绩20...");
scores.remove(Integer.valueOf(20));
System.out.println("-> 修改完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});

reader.start();
writer.start();
latch.await();

System.out.println("最终列表: " + scores);

输出结果:

初始成绩列表: [10, 20, 30, 40, 50]
读取线程看到成绩: 10
-> 修改线程删除成绩20...
-> 修改完成
!!! 检测到并发修改,快速失败!
最终列表: [10, 30, 40, 50]

底层实现:

public E next() {
// 检查并发修改
checkForComodification();
// 返回下一个元素
return (E) elementData[lastRet = i];
}

final void checkForComodification() {
// modCount与expectedModCount不一致则抛异常
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

fail-safe(安全失败):

设计思想: 即使发生错误也能继续运行,适合不稳定环境。

CopyOnWriteArrayList示例:

public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 复制数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 在副本上添加
newElements[len] = e;
// 更新引用
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

优缺点对比:

机制优点缺点适用场景
fail-fast及时发现问题
避免数据不一致
不支持并发单线程环境
fail-safe支持并发读写内存开销大
无法保证实时性
读多写少

Set接口详解

Set的底层实现

底层结构说明:

  • HashSet: 基于HashMap,元素作为key存储,value为固定对象
  • LinkedHashSet: HashSet的子类,通过链表维护插入顺序
  • TreeSet: 基于红黑树,自动排序

无序性和不可重复性

无序性:

  • 不是随机性
  • 元素存储位置由哈希值决定
  • 不按数组索引顺序存储

不可重复性:

  • 通过equals()判断
  • 需要同时重写equals()和hashCode()

示例:

public class Student {
private String name;
private int age;

// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}

// 重写equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}

// 重写hashCode
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}

// 使用HashSet
Set<Student> students = new HashSet<>();
students.add(new Student("张三", 20));
students.add(new Student("张三", 20)); // 重复,不会添加
students.add(new Student("李四", 21));

System.out.println("学生人数: " + students.size()); // 输出: 2

HashSet vs LinkedHashSet vs TreeSet

全面对比:

特性HashSetLinkedHashSetTreeSet
底层结构HashMapLinkedHashMap红黑树
有序性无序插入顺序自然/定制排序
查询性能O(1)O(1)O(logn)
null值允许一个允许一个不允许
线程安全
使用场景快速去重保持顺序去重需要排序

使用示例:

// HashSet - 无序
Set<String> hashSet = new HashSet<>();
hashSet.add("香蕉");
hashSet.add("苹果");
hashSet.add("橘子");
System.out.println(hashSet); // 输出顺序不确定

// LinkedHashSet - 保持插入顺序
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("香蕉");
linkedSet.add("苹果");
linkedSet.add("橘子");
System.out.println(linkedSet); // [香蕉, 苹果, 橘子]

// TreeSet - 自然排序
Set<String> treeSet = new TreeSet<>();
treeSet.add("香蕉");
treeSet.add("苹果");
treeSet.add("橘子");
System.out.println(treeSet); // [苹果, 橘子, 香蕉] (拼音排序)

Comparable vs Comparator

两个接口都用于对象排序,但使用方式不同:

Comparable接口 - 内部排序:

// 商品类实现Comparable接口
public class Product implements Comparable<Product> {
private String name;
private double price;

public Product(String name, double price) {
this.name = name;
this.price = price;
}

// 实现compareTo方法 - 按价格排序
@Override
public int compareTo(Product other) {
return Double.compare(this.price, other.price);
}

@Override
public String toString() {
return name + ": ¥" + price;
}
}

// 使用TreeSet自动排序
TreeSet<Product> products = new TreeSet<>();
products.add(new Product("键盘", 299.0));
products.add(new Product("鼠标", 89.0));
products.add(new Product("显示器", 1299.0));

System.out.println("按价格排序:");
for (Product p : products) {
System.out.println(p);
}
// 输出:
// 鼠标: ¥89.0
// 键盘: ¥299.0
// 显示器: ¥1299.0

Comparator接口 - 外部排序:

// 定义多个比较器
ArrayList<Integer> nums = new ArrayList<>();
nums.add(50);
nums.add(30);
nums.add(80);
nums.add(10);
nums.add(60);

System.out.println("原始数据: " + nums);

// 自然排序(升序)
Collections.sort(nums);
System.out.println("升序排序: " + nums);

// 定制排序(降序)
Collections.sort(nums, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("降序排序: " + nums);

// Lambda表达式简化
Collections.sort(nums, (o1, o2) -> o1.compareTo(o2));
System.out.println("升序排序: " + nums);

选择建议:

  • 使用Comparable: 当类有一个默认的自然排序
  • 使用Comparator: 需要多种排序方式或不能修改类源码

Queue接口详解

Queue接口的详细内容已拆分至独立文档,请参阅 Queue接口详解

Map接口详解

Map的底层实现

底层结构说明:

  • HashMap:

    • JDK 1.7: 数组+链表
    • JDK 1.8+: 数组+链表+红黑树(链表长度>8且数组长度≥64时转换)
  • LinkedHashMap: 继承HashMap,增加双向链表维护顺序

  • TreeMap: 红黑树实现,key自动排序

  • Hashtable: 类似HashMap但线程安全,已过时

集合的遍历方式

Collection的遍历方式

示例代码:

List<String> courses = Arrays.asList("Java", "Python", "Go", "C++");

// 1. 传统for循环
for (int i = 0; i < courses.size(); i++) {
System.out.println(courses.get(i));
}

// 2. Iterator迭代器
Iterator<String> iterator = courses.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}

// 3. foreach循环
for (String course : courses) {
System.out.println(course);
}

// 4. Stream API
courses.stream().forEach(System.out::println);

Iterable vs Iterator

职责分离:

  • Iterable: 表示集合可迭代
  • Iterator: 定义迭代的具体方式

实现示例:

class MyList<E> implements Iterable<E> {
private Object[] elements;
private int size;

// 实现Iterable接口
public Iterator<E> iterator() {
return new MyIterator();
}

// 内部Iterator实现类
private class MyIterator implements Iterator<E> {
private int cursor = 0;

public boolean hasNext() {
return cursor < size;
}

public E next() {
return (E) elements[cursor++];
}
}
}

集合选择指南

根据特性选择

使用建议总结

List选择:

  • 默认选择: ArrayList
  • 频繁增删: ArrayList(不是LinkedList!)
  • 线程安全: CopyOnWriteArrayList

Set选择:

  • 快速去重: HashSet
  • 保持顺序: LinkedHashSet
  • 自动排序: TreeSet

Queue选择:

  • 普通队列: ArrayDeque
  • 优先队列: PriorityQueue
  • 阻塞队列: ArrayBlockingQueue

Map选择:

  • 默认选择: HashMap
  • 保持顺序: LinkedHashMap
  • 自动排序: TreeMap
  • 线程安全: ConcurrentHashMap

Collection vs Collections

本质区别

Collection:

  • 集合框架的根接口
  • 定义通用的集合操作
  • 有众多实现类

Collections:

  • 工具类,不能实例化
  • 提供静态方法操作集合
  • 类似Arrays之于数组

Collections常用方法

List<Integer> nums = new ArrayList<>(Arrays.asList(5, 2, 8, 1, 9));

// 排序
Collections.sort(nums);
System.out.println(nums); // [1, 2, 5, 8, 9]

// 反转
Collections.reverse(nums);
System.out.println(nums); // [9, 8, 5, 2, 1]

// 打乱
Collections.shuffle(nums);
System.out.println(nums); // 随机顺序

// 最大值/最小值
int max = Collections.max(nums);
int min = Collections.min(nums);

// 线程安全包装
List<Integer> syncList = Collections.synchronizedList(nums);
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());

// 不可变集合
List<String> immutableList = Collections.unmodifiableList(nums);

总结

核心知识点回顾

  1. 集合框架结构: Collection和Map两大体系
  2. List实现类: ArrayList(数组)和LinkedList(链表)
  3. Set实现类: HashSet、LinkedHashSet、TreeSet
  4. Queue实现类: PriorityQueue、ArrayDeque、BlockingQueue
  5. Map实现类: HashMap、LinkedHashMap、TreeMap
  6. 并发安全: fail-fast和fail-safe机制
  7. 排序接口: Comparable和Comparator
  8. 遍历方式: for、Iterator、foreach、Stream

面试重点

掌握这些知识点,就能应对大部分Java集合相关的面试问题了!