1. List集合基础与核心操作
Java中的List接口是最常用的集合类型之一,它代表一个有序的元素序列。与Set不同,List允许重复元素,并且每个元素都有明确的索引位置。我们先来看一个典型示例:
java复制List<String> fruits = new ArrayList<>();
fruits.add("Apple"); // 索引0
fruits.add("Banana"); // 索引1
fruits.add("Apple"); // 允许重复元素
System.out.println(fruits.get(1)); // 输出: Banana
List的核心特性包括:
- 有序性:元素按照插入顺序存储
- 可重复性:允许存储相同元素的多个实例
- 索引访问:可以通过整数索引直接访问元素
1.1 常用List实现类对比
Java集合框架提供了多个List实现,最常用的有三种:
| 实现类 | 底层结构 | 随机访问效率 | 插入删除效率 | 线程安全 | 适用场景 |
|---|---|---|---|---|---|
| ArrayList | 动态数组 | O(1) | O(n) | 不安全 | 查询多、修改少的场景 |
| LinkedList | 双向链表 | O(n) | O(1) | 不安全 | 频繁插入删除的场景 |
| Vector | 动态数组 | O(1) | O(n) | 安全 | 需要线程安全的场景 |
| CopyOnWriteArrayList | 动态数组 | O(1) | O(n) | 安全 | 读多写少的并发场景 |
提示:在Java 5+环境中,需要线程安全时优先考虑CopyOnWriteArrayList而非Vector,因为Vector的所有方法都是同步的,性能开销较大。
1.2 关键API使用技巧
List接口提供了丰富的方法,这里介绍几个容易被误用的方法:
java复制// 1. subList的陷阱
List<Integer> numbers = new ArrayList<>(Arrays.asList(1,2,3,4,5));
List<Integer> sub = numbers.subList(1, 3); // [2,3]
sub.clear(); // 这会同时修改原始numbers列表!
// 2. 正确的批量操作
List<String> list1 = new ArrayList<>(Arrays.asList("A","B","C"));
List<String> list2 = new ArrayList<>(Arrays.asList("B","C","D"));
list1.retainAll(list2); // list1变为[B,C](交集)
list1.removeAll(list2); // list1变为空列表
// 3. 高效的列表初始化
List<String> initializedList = new ArrayList<String>() {{
add("Java");
add("Python");
add("Go");
}};
2. 深入理解泛型机制
泛型是Java 5引入的核心特性,它为集合提供了编译时类型安全检查。我们先看一个典型问题:
java复制// 非泛型代码的问题
List rawList = new ArrayList();
rawList.add("String");
rawList.add(10); // 编译通过,运行时可能出错
String s = (String) rawList.get(1); // 运行时ClassCastException
// 泛型解决方案
List<String> safeList = new ArrayList<>();
safeList.add("TypeSafe");
// safeList.add(10); // 编译时报错
2.1 泛型类型擦除原理
Java泛型是通过类型擦除实现的,这意味着泛型信息只存在于编译阶段。例如:
java复制List<String> stringList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
System.out.println(stringList.getClass() == intList.getClass()); // 输出true
类型擦除带来的限制包括:
- 不能创建泛型数组:
new List<String>[10]是非法的 - 不能使用基本类型作为类型参数:必须用
List<Integer>而非List<int> - 运行时无法获取泛型的具体类型信息
2.2 泛型通配符高级用法
通配符?提供了更灵活的泛型使用方式:
java复制// 上界通配符 - 允许读取
List<? extends Number> numbers = new ArrayList<Double>();
Number n = numbers.get(0); // 安全读取
// numbers.add(3.14); // 编译错误 - 不安全操作
// 下界通配符 - 允许写入
List<? super Integer> integers = new ArrayList<Number>();
integers.add(100); // 安全写入
// Integer i = integers.get(0); // 编译错误 - 不安全读取
// 无界通配符 - 仅允许Object操作
List<?> unknownList = new ArrayList<String>();
Object o = unknownList.get(0);
// unknownList.add("anything"); // 编译错误
3. List与泛型的结合实践
3.1 类型安全的集合操作
泛型与集合结合的最佳实践示例:
java复制// 安全的泛型方法
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T item : src) {
dest.add(item);
}
}
List<Number> numbers = new ArrayList<>();
List<Integer> integers = Arrays.asList(1, 2, 3);
copy(numbers, integers); // 安全地将Integer列表复制到Number列表
3.2 自定义泛型List处理器
实现一个可以处理多种List类型的工具类:
java复制public class ListProcessor<T> {
private final List<T> items;
public ListProcessor(List<T> items) {
this.items = new ArrayList<>(items); // 防御性拷贝
}
public List<T> filter(Predicate<? super T> predicate) {
return items.stream()
.filter(predicate)
.collect(Collectors.toList());
}
public <R> List<R> transform(Function<? super T, ? extends R> mapper) {
return items.stream()
.map(mapper)
.collect(Collectors.toList());
}
public void processAll(Consumer<? super T> action) {
items.forEach(action);
}
}
// 使用示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
ListProcessor<String> processor = new ListProcessor<>(names);
List<String> filtered = processor.filter(s -> s.length() > 3);
List<Integer> lengths = processor.transform(String::length);
processor.processAll(System.out::println);
4. 性能优化与最佳实践
4.1 List初始化容量优化
ArrayList在内部使用数组实现,不恰当的初始化会导致频繁扩容:
java复制// 不好的做法 - 默认初始容量10,频繁扩容
List<String> list1 = new ArrayList<>();
// 好的做法 - 预估容量
List<String> list2 = new ArrayList<>(1000);
// 动态扩容测试
long start = System.nanoTime();
List<Integer> testList = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
testList.add(i); // 多次扩容
}
long end = System.nanoTime();
System.out.println("未预设容量耗时: " + (end - start) + "ns");
start = System.nanoTime();
List<Integer> optimizedList = new ArrayList<>(1_000_000);
for (int i = 0; i < 1_000_000; i++) {
optimizedList.add(i); // 无扩容
}
end = System.nanoTime();
System.out.println("预设容量耗时: " + (end - start) + "ns");
4.2 遍历方式性能对比
不同遍历方式的性能差异:
java复制List<Integer> numbers = IntStream.range(0, 10_000_000)
.boxed()
.collect(Collectors.toList());
// 1. 传统for循环
long start = System.currentTimeMillis();
for (int i = 0; i < numbers.size(); i++) {
int val = numbers.get(i);
}
long duration = System.currentTimeMillis() - start;
System.out.println("传统for循环: " + duration + "ms");
// 2. 增强for循环
start = System.currentTimeMillis();
for (int num : numbers) {
int val = num;
}
duration = System.currentTimeMillis() - start;
System.out.println("增强for循环: " + duration + "ms");
// 3. 迭代器方式
start = System.currentTimeMillis();
for (Iterator<Integer> it = numbers.iterator(); it.hasNext();) {
int val = it.next();
}
duration = System.currentTimeMillis() - start;
System.out.println("迭代器方式: " + duration + "ms");
// 4. forEach方法
start = System.currentTimeMillis();
numbers.forEach(val -> { int x = val; });
duration = System.currentTimeMillis() - start;
System.out.println("forEach方法: " + duration + "ms");
// 5. 并行流
start = System.currentTimeMillis();
numbers.parallelStream().forEach(val -> { int x = val; });
duration = System.currentTimeMillis() - start;
System.out.println("并行流: " + duration + "ms");
注意:对于ArrayList,随机访问效率最高,传统for循环最快;但对于LinkedList,使用迭代器或增强for循环更高效。
4.3 不可变列表的最佳实践
创建不可变列表的几种方式及其区别:
java复制// 1. Collections.unmodifiableList
List<String> mutable = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> unmodifiable = Collections.unmodifiableList(mutable);
mutable.add("D"); // 会影响到unmodifiable!
// unmodifiable.add("E"); // 抛出UnsupportedOperationException
// 2. Arrays.asList
List<String> asList = Arrays.asList("A", "B", "C");
// asList.add("D"); // 抛出UnsupportedOperationException
// asList.set(0, "AA"); // 允许修改已有元素
// 3. List.of (Java 9+)
List<String> listOf = List.of("A", "B", "C");
// listOf.add("D"); // 抛出UnsupportedOperationException
// listOf.set(0, "AA"); // 抛出UnsupportedOperationException
// 4. Guava ImmutableList
List<String> guavaImmutable = ImmutableList.of("A", "B", "C");
// 任何修改操作都会抛出UnsupportedOperationException
在实际项目中,我推荐:
- 如果确定集合完全不需要修改,使用
List.of() - 如果需要防御性编程,使用
Collections.unmodifiableList(new ArrayList<>(original)) - 考虑使用Guava的ImmutableList,它提供了更丰富的构建方式
5. 常见问题与解决方案
5.1 并发修改异常处理
ConcurrentModificationException是操作集合时的常见问题:
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误示例 - 在迭代中修改
try {
for (String s : list) {
if ("B".equals(s)) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
} catch (Exception e) {
System.out.println("错误: " + e);
}
// 解决方案1 - 使用迭代器的remove方法
for (Iterator<String> it = list.iterator(); it.hasNext();) {
String s = it.next();
if ("B".equals(s)) {
it.remove(); // 安全删除
}
}
// 解决方案2 - 使用Java 8+ removeIf
list.removeIf(s -> "B".equals(s));
// 解决方案3 - 使用CopyOnWriteArrayList (并发场景)
List<String> cowList = new CopyOnWriteArrayList<>(list);
for (String s : cowList) {
if ("B".equals(s)) {
cowList.remove(s); // 安全但性能较低
}
}
5.2 泛型数组创建方案
由于类型擦除,直接创建泛型数组是非法的,但可以通过以下方式解决:
java复制// 非法操作
// T[] array = new T[10];
// 解决方案1 - 使用Object数组转换
@SuppressWarnings("unchecked")
public <T> T[] createArray(Class<T> clazz, int size) {
return (T[]) Array.newInstance(clazz, size);
}
// 解决方案2 - 使用列表代替数组
public <T> List<T> createList(int size) {
return new ArrayList<T>(size);
}
// 特殊案例 - 可变参数中的泛型数组
@SafeVarargs
public final <T> List<T> asList(T... items) {
return Arrays.asList(items);
}
5.3 类型安全的异构容器
当需要以类型安全的方式存储多种类型对象时,可以使用异构容器模式:
java复制public class TypeSafeContainer {
private Map<Class<?>, Object> container = new HashMap<>();
public <T> void put(Class<T> type, T instance) {
container.put(Objects.requireNonNull(type), type.cast(instance));
}
public <T> T get(Class<T> type) {
return type.cast(container.get(type));
}
}
// 使用示例
TypeSafeContainer container = new TypeSafeContainer();
container.put(String.class, "Hello");
container.put(Integer.class, 42);
String s = container.get(String.class);
Integer i = container.get(Integer.class);
// Double d = container.get(Double.class); // 返回null
6. Java 8+中的新特性应用
6.1 Stream API与List转换
Java 8引入的Stream API为集合操作带来了革命性变化:
java复制List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 1. 过滤与收集
List<String> longNames = names.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
// 2. 映射转换
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 3. 排序
List<String> sorted = names.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// 4. 去重
List<String> unique = names.stream()
.distinct()
.collect(Collectors.toList());
// 5. 统计
IntSummaryStatistics stats = names.stream()
.mapToInt(String::length)
.summaryStatistics();
System.out.println("平均长度: " + stats.getAverage());
6.2 并行流性能考量
并行流可以提升处理效率,但需谨慎使用:
java复制List<Integer> numbers = IntStream.range(0, 10_000_000)
.boxed()
.collect(Collectors.toList());
// 顺序处理
long start = System.currentTimeMillis();
long count = numbers.stream()
.filter(n -> n % 2 == 0)
.count();
long duration = System.currentTimeMillis() - start;
System.out.println("顺序流耗时: " + duration + "ms, 结果: " + count);
// 并行处理
start = System.currentTimeMillis();
count = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.count();
duration = System.currentTimeMillis() - start;
System.out.println("并行流耗时: " + duration + "ms, 结果: " + count);
注意:并行流适用于大数据集和计算密集型操作,对于小数据集或I/O密集型操作,并行可能反而降低性能。
7. 实际项目经验分享
7.1 性能敏感场景的优化
在高性能场景下,我总结了以下优化经验:
- 批量操作优于单条操作:
java复制// 不好 - 每次add都可能导致扩容
for (int i = 0; i < 1000; i++) {
list.add(data[i]);
}
// 好 - 一次性确保容量
list.ensureCapacity(1000);
for (int i = 0; i < 1000; i++) {
list.add(data[i]);
}
// 更好 - 使用addAll
list.addAll(Arrays.asList(data));
- 选择合适的实现类:
- 随机访问多 → ArrayList
- 频繁在首尾增删 → LinkedList
- 并发读多写少 → CopyOnWriteArrayList
- 需要线程安全且修改频繁 → Collections.synchronizedList
- 避免不必要的装箱:
java复制// 不好 - 自动装箱开销
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add(i); // 发生自动装箱
}
// 好 - 使用原始类型特化集合
IntList intList = new IntArrayList(); // 第三方库如Eclipse Collections
for (int i = 0; i < 1000000; i++) {
intList.add(i); // 无装箱开销
}
7.2 防御性编程实践
在API设计中,正确处理集合参数和返回值至关重要:
java复制public class CollectionUtils {
// 不好的API设计 - 暴露内部引用
public static List<String> getNames() {
return new ArrayList<>(Arrays.asList("A", "B", "C"));
}
// 好的API设计 - 返回不可变视图
public static List<String> getSafeNames() {
return Collections.unmodifiableList(
new ArrayList<>(Arrays.asList("A", "B", "C")));
}
// 处理客户端传入的集合
public static void processNames(List<String> names) {
// 防御性拷贝
List<String> copy = new ArrayList<>(
Objects.requireNonNull(names));
// 处理逻辑...
}
}
7.3 与框架整合的经验
与主流框架整合时的注意事项:
- Hibernate/JPA集合映射:
java复制@Entity
public class Department {
@Id private Long id;
// 使用接口类型声明
@OneToMany(mappedBy = "department")
private List<Employee> employees = new ArrayList<>();
// 提供安全的访问方法
public List<Employee> getEmployees() {
return Collections.unmodifiableList(employees);
}
public void addEmployee(Employee e) {
e.setDepartment(this);
employees.add(e);
}
}
- Spring MVC参数绑定:
java复制@RestController
public class MyController {
// 自动绑定数组参数
@GetMapping("/users")
public List<User> getUsers(@RequestParam("id") List<Long> ids) {
return userService.findAllById(ids);
}
// 处理JSON数组请求体
@PostMapping("/users")
public void createUsers(@RequestBody List<@Valid User> users) {
userService.saveAll(users);
}
}
- Jackson泛型反序列化:
java复制// 处理泛型集合的JSON序列化
public class Response<T> {
private List<T> data;
private int count;
// 必须提供默认构造器
public Response() {}
// 必须提供类型保留的构造器
@JsonCreator
public Response(@JsonProperty("data") List<T> data,
@JsonProperty("count") int count) {
this.data = new ArrayList<>(data);
this.count = count;
}
// 获取数据时提供不可变视图
public List<T> getData() {
return Collections.unmodifiableList(data);
}
}
// 使用示例
String json = "{\"data\":[\"A\",\"B\",\"C\"],\"count\":3}";
Response<String> response = objectMapper.readValue(json,
new TypeReference<Response<String>>() {});
