1. List集合基础与核心特性
Java中的List接口是最常用的集合类型之一,它代表一个有序的集合(也称为序列)。与数组不同,List的大小可以动态变化,这为开发者提供了极大的灵活性。List允许重复元素,并且每个元素都有其特定的索引位置,这使得我们可以精确控制元素的存储位置。
List接口的主要实现类包括:
- ArrayList:基于动态数组实现,随机访问速度快(O(1)),但在中间插入/删除元素性能较差(O(n))
- LinkedList:基于双向链表实现,插入/删除操作快(O(1)),但随机访问性能较差(O(n))
- Vector:线程安全的ArrayList实现,但性能较差,已逐渐被Collections.synchronizedList替代
实际开发中,ArrayList是最常用的选择,除非你需要频繁在列表中间插入/删除元素,才考虑使用LinkedList。
List的基本操作示例:
List<String> fruits = new ArrayList<>(); fruits.add("Apple"); // 添加元素 fruits.add("Banana"); fruits.add(1, "Orange"); // 在指定位置插入 String fruit = fruits.get(0); // 获取元素 fruits.remove("Banana"); // 删除元素 int size = fruits.size(); // 获取大小2. 深入理解泛型机制
泛型是Java 5引入的重要特性,它允许在编译时检查类型安全,避免了运行时的ClassCastException。在集合框架中,泛型的使用尤为重要,它让我们可以明确指定集合中存储的元素类型。
泛型的核心优势:
- 类型安全:编译器可以在编译时检查类型是否正确
- 消除强制类型转换:减少代码中的显式类型转换
- 提高代码可读性:明确表明集合中存储的元素类型
泛型的基本语法:
List<String> stringList = new ArrayList<String>(); // Java 7+ 可以使用菱形语法 List<Integer> intList = new ArrayList<>();泛型方法示例:
public static <T> void printList(List<T> list) { for (T element : list) { System.out.println(element); } }泛型边界(Bounded Type):
// 只接受Number及其子类 public static <T extends Number> double sum(List<T> numbers) { double total = 0.0; for (Number num : numbers) { total += num.doubleValue(); } return total; }3. List集合的高级操作与性能优化
3.1 批量操作与集合工具类
List接口提供了丰富的批量操作方法:
List<String> list1 = new ArrayList<>(Arrays.asList("A", "B", "C")); List<String> list2 = new ArrayList<>(Arrays.asList("B", "C", "D")); list1.addAll(list2); // 合并两个集合 list1.retainAll(list2); // 取交集 list1.removeAll(list2); // 移除共有元素Collections工具类提供了许多实用方法:
List<Integer> numbers = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9)); Collections.sort(numbers); // 排序 Collections.reverse(numbers); // 反转 Collections.shuffle(numbers); // 随机打乱 int freq = Collections.frequency(numbers, 1); // 出现频率3.2 性能优化实践
- 初始化时指定容量:对于ArrayList,如果能预估大小,初始化时指定容量可以避免多次扩容
List<String> largeList = new ArrayList<>(1000);- 使用subList的注意事项:
List<String> original = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); List<String> sub = original.subList(1, 3); // 获取子列表[B, C] sub.clear(); // 会同时影响original列表- 遍历方式的选择:
- 随机访问列表(如ArrayList):for循环或forEach
- 顺序访问列表(如LinkedList):使用迭代器
- 避免在循环中修改列表:
// 错误方式 - 可能抛出ConcurrentModificationException for (String item : list) { if (condition) { list.remove(item); } } // 正确方式 - 使用迭代器 Iterator<String> it = list.iterator(); while (it.hasNext()) { String item = it.next(); if (condition) { it.remove(); } }4. 泛型的高级特性与类型擦除
4.1 通配符与上下界
Java泛型提供了通配符(?)来增加灵活性:
无界通配符:
public static void printList(List<?> list) { for (Object elem : list) { System.out.println(elem); } }上界通配符:
public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) { s += n.doubleValue(); } return s; }下界通配符:
public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }4.2 类型擦除与桥方法
Java的泛型是通过类型擦除实现的,这意味着在运行时泛型类型信息会被擦除。例如:
List<String> stringList = new ArrayList<>(); List<Integer> intList = new ArrayList<>(); // 运行时两者的类型都是List,没有String和Integer的区别编译器会通过生成桥方法来保持多态性。例如:
class MyList implements List<String> { // 编译器会生成桥方法确保类型安全 }4.3 泛型数组的限制
由于类型擦除,Java不允许直接创建泛型数组:
// 编译错误 List<String>[] arrayOfLists = new List<String>[10]; // 正确方式:使用通配符类型 List<?>[] arrayOfLists = new List<?>[10];5. 实际应用中的最佳实践
5.1 集合与泛型在框架中的应用
- Spring框架中的依赖注入:
@Autowired private List<Validator> validators; // 注入所有Validator实现- JPA/Hibernate中的关系映射:
@Entity public class Department { @OneToMany(mappedBy = "department") private List<Employee> employees; }5.2 常见问题与解决方案
- 类型安全混合集合:
List list = new ArrayList(); list.add("string"); list.add(1); // 允许但不安全 // 安全方式 List<Object> safeList = new ArrayList<>(); safeList.add("string"); safeList.add(1); // 明确知道是Object类型- 泛型与可变参数:
@SafeVarargs // 添加安全注解 public static <T> List<T> asList(T... elements) { List<T> list = new ArrayList<>(); for (T element : elements) { list.add(element); } return list; }- 不可变列表的创建:
List<String> immutableList = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("A", "B", "C"))); // Java 9+ List<String> immutableList = List.of("A", "B", "C");5.3 性能对比与选择建议
集合类型选择指南:
- 需要快速随机访问 → ArrayList
- 频繁在列表中间插入/删除 → LinkedList
- 需要线程安全 → CopyOnWriteArrayList(读多写少场景)
- 需要保持插入顺序且去重 → LinkedHashSet
内存占用比较(存储100万个整数):
- ArrayList:约40MB
- LinkedList:约80MB(每个元素需要额外的前后指针)
操作时间复杂度对比:
| 操作 | ArrayList | LinkedList |
|---|---|---|
| get(index) | O(1) | O(n) |
| add(element) | O(1) | O(1) |
| add(index) | O(n) | O(1) |
| remove(index) | O(n) | O(1) |
6. Java 8+ 中的新特性应用
6.1 Stream API 与集合操作
Java 8引入的Stream API为集合操作提供了更强大的功能:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); // 过滤和转换 List<String> result = names.stream() .filter(name -> name.length() > 4) .map(String::toUpperCase) .collect(Collectors.toList()); // 分组操作 Map<Integer, List<String>> groupedByNameLength = names.stream() .collect(Collectors.groupingBy(String::length));6.2 不可变集合的便捷创建
Java 9引入了更简洁的不可变集合创建方式:
List<String> immutableList = List.of("A", "B", "C"); Set<Integer> immutableSet = Set.of(1, 2, 3); Map<String, Integer> immutableMap = Map.of("A", 1, "B", 2);6.3 集合工厂方法
Java 10引入了集合工厂方法,进一步简化集合创建:
List<String> list = List.copyOf(anotherList); // 创建不可变副本7. 面试常见问题解析
7.1 List与Set的区别
- List是有序集合,允许重复元素;Set是无序集合,不允许重复
- List通过索引访问元素;Set通过元素本身访问
- List通常允许null元素;某些Set实现不允许null
7.2 ArrayList与LinkedList的区别
- 底层实现:ArrayList基于动态数组,LinkedList基于双向链表
- 随机访问:ArrayList O(1),LinkedList O(n)
- 插入删除:ArrayList平均O(n),LinkedList O(1)(如果已知位置)
- 内存占用:LinkedList每个元素需要额外存储前后指针
7.3 泛型擦除相关问题
- 为什么不能直接创建泛型数组?
- 因为类型擦除会导致运行时类型检查失效,可能引发类型安全问题
- 如何绕过泛型擦除的限制?
- 使用Class对象作为运行时类型标记
- 通过反射获取类型信息
- 泛型方法中的类型推断:
// 编译器会根据参数推断T的类型 <T> T getFirst(List<T> list) { return list.get(0); } String s = getFirst(Arrays.asList("A", "B")); // T被推断为String7.4 并发环境下的集合使用
- 快速失败(Fail-Fast)机制:
- ArrayList等非线程安全集合在迭代时如果被修改,会抛出ConcurrentModificationException
- 线程安全替代方案:
- CopyOnWriteArrayList:适合读多写少场景
- Collections.synchronizedList:包装普通List使其线程安全
- 并发修改的检测:
List<String> list = new ArrayList<>(); list.add("A"); list.add("B"); // 会抛出ConcurrentModificationException for (String s : list) { if (s.equals("A")) { list.remove(s); } }