Java 集合实战使用手册


一、集合框架全景图

                        Iterable

                       Collection
                     ┌─────┼──────────┐
                    List   Set       Queue
                     │      │          │
              ┌──────┤   ┌──┴───┐   ┌──┴──────┐
         ArrayList  LinkedList  │   PriorityQueue  Deque
                    HashSet TreeSet         │
                      │                ArrayDeque
                LinkedHashSet

                        Map
                    ┌────┼────┐
                HashMap  TreeMap  Hashtable

             LinkedHashMap

        ─── 线程安全版本 ───
        ConcurrentHashMap
        CopyOnWriteArrayList
        CopyOnWriteArraySet
        ConcurrentLinkedQueue
        BlockingQueue (接口)
           ├── ArrayBlockingQueue
           ├── LinkedBlockingQueue
           └── PriorityBlockingQueue

一句话记忆:Collection 管”一组对象”,Map 管”键值对”。选集合就是选数据结构。


二、场景选型指南

2.1 List 选型

场景推荐原因
90% 的业务场景ArrayList连续内存,随机访问 O(1),CPU 缓存友好
频繁在头部插入/删除LinkedList头部操作 O(1),但实际场景极少
多线程读多写少CopyOnWriteArrayList读无锁,写时复制
需要线程安全 + 高并发写Collections.synchronizedList 或并发队列根据具体场景选择

⚠️ 真相:99% 的场景直接用 ArrayList。LinkedList 在实际项目中几乎不用,因为即使是中间插入,ArrayList 的数组拷贝也比 LinkedList 的指针遍历快(CPU 缓存行优势)。

2.2 Set 选型

场景推荐原因
去重(不关心顺序)HashSetO(1) 查找
去重 + 保持插入顺序LinkedHashSet维护插入顺序的链表
去重 + 排序TreeSet红黑树,自动排序
多线程去重ConcurrentHashMap.newKeySet()JDK8+ 推荐方式

2.3 Map 选型

场景推荐原因
通用键值映射HashMapO(1) 读写,最常用
需要按 key 排序TreeMap红黑树,key 有序
需要保持插入顺序LinkedHashMap维护双向链表
实现 LRU 缓存LinkedHashMap(accessOrder=true)自带 LRU 支持
高并发场景ConcurrentHashMap分段锁/CAS,线程安全
枚举作为 keyEnumMap数组实现,极致性能

⚠️ 永远不要用 Hashtable 和 Vector,它们是 JDK1.0 的遗留类,所有方法加 synchronized,性能差,已被淘汰。

2.4 Queue 选型

场景推荐原因
普通队列/栈ArrayDeque比 LinkedList 和 Stack 都快
优先级排序PriorityQueue最小堆实现
生产者-消费者模式LinkedBlockingQueue阻塞队列,线程安全
有界阻塞队列ArrayBlockingQueue固定容量,防 OOM
延迟任务DelayQueue元素到期才能取出

三、企业级使用实战

3.1 ArrayList:业务数据处理

// 场景:从数据库查出用户列表,进行过滤和转换
public List<UserVO> getActiveUsers(Long departmentId) {
    List<UserPO> users = userMapper.selectByDepartment(departmentId);

    // ✅ 预估容量,避免扩容开销
    List<UserVO> result = new ArrayList<>(users.size());

    for (UserPO user : users) {
        if (user.getStatus() == UserStatus.ACTIVE) {
            result.add(convertToVO(user));
        }
    }
    return result;
}

// ✅ Stream 写法(更简洁,企业中更常见)
public List<UserVO> getActiveUsersStream(Long departmentId) {
    return userMapper.selectByDepartment(departmentId).stream()
            .filter(u -> u.getStatus() == UserStatus.ACTIVE)
            .map(this::convertToVO)
            .collect(Collectors.toList()); // JDK16+ 可用 .toList()
}

3.2 HashMap:业务数据分组与缓存

// 场景:将订单按用户 ID 分组
public Map<Long, List<Order>> groupOrdersByUser(List<Order> orders) {
    // ✅ 方式一:手动分组
    Map<Long, List<Order>> map = new HashMap<>(orders.size() * 4 / 3 + 1);
    // 为什么是 size * 4/3 + 1?因为 HashMap 默认负载因子 0.75
    // 预设容量 = 期望元素数 / 0.75,避免 rehash

    for (Order order : orders) {
        map.computeIfAbsent(order.getUserId(), k -> new ArrayList<>())
           .add(order);
    }
    return map;

    // ✅ 方式二:Stream(推荐)
    // return orders.stream().collect(Collectors.groupingBy(Order::getUserId));
}

// 场景:本地缓存(简单场景,不需要 Redis/Caffeine 时)
public class LocalConfigCache {
    // ✅ ConcurrentHashMap 保证线程安全
    private final Map<String, String> cache = new ConcurrentHashMap<>(256);

    public String getConfig(String key) {
        return cache.computeIfAbsent(key, k -> {
            // 从数据库加载配置
            return configMapper.selectByKey(k);
        });
    }

    public void refresh(String key) {
        cache.remove(key);
    }
}

3.3 LinkedHashMap:实现 LRU 缓存

// 场景:用 LinkedHashMap 实现一个简单的 LRU 缓存
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxCapacity;

    public LRUCache(int maxCapacity) {
        // initialCapacity: 容量
        // loadFactor: 0.75(默认)
        // accessOrder: true —— 关键!按访问顺序排列,最近访问的在尾部
        super(maxCapacity * 4 / 3 + 1, 0.75f, true);
        this.maxCapacity = maxCapacity;
    }

    // 当 put 新元素后,如果超出容量,自动移除最老的元素(链表头部)
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxCapacity;
    }
}

// 使用
LRUCache<String, Object> cache = new LRUCache<>(1000);
cache.put("user:1001", userObj);
Object val = cache.get("user:1001"); // 访问后,这个元素移到尾部,不会被淘汰

⚠️ 这个 LRU 是非线程安全的。多线程环境下请用 Collections.synchronizedMap() 包装,或者直接用 Caffeine。

3.4 ConcurrentHashMap:并发计数器

// 场景:统计接口调用次数(高并发)
public class ApiCounter {
    private final ConcurrentHashMap<String, LongAdder> counterMap = new ConcurrentHashMap<>();

    // ✅ 原子性累加,无锁竞争
    public void increment(String apiPath) {
        counterMap.computeIfAbsent(apiPath, k -> new LongAdder()).increment();
    }

    public long getCount(String apiPath) {
        LongAdder adder = counterMap.get(apiPath);
        return adder == null ? 0 : adder.sum();
    }

    public Map<String, Long> getAllCounts() {
        Map<String, Long> result = new HashMap<>(counterMap.size() * 4 / 3 + 1);
        counterMap.forEach((k, v) -> result.put(k, v.sum()));
        return result;
    }
}

⚠️ 不要用 AtomicLong,高并发下 CAS 自旋严重。LongAdder 通过分段累加大幅降低竞争。

3.5 PriorityQueue:任务优先级调度

// 场景:处理工单,优先级高的先处理
public class TicketScheduler {
    // 按优先级排序,数字越小优先级越高
    private final PriorityQueue<Ticket> queue = new PriorityQueue<>(
            Comparator.comparingInt(Ticket::getPriority)
                       .thenComparing(Ticket::getCreateTime) // 同优先级按时间排
    );

    public void submit(Ticket ticket) {
        queue.offer(ticket);
    }

    public Ticket takeNext() {
        return queue.poll(); // 取出优先级最高的
    }
}

3.6 ArrayDeque:替代 Stack

// ⚠️ 永远不要用 java.util.Stack(继承自 Vector,全方法加锁,还是 LIFO 接口设计错误)
// ✅ 用 ArrayDeque 作为栈
Deque<String> stack = new ArrayDeque<>();
stack.push("first");
stack.push("second");
String top = stack.pop(); // "second"

// ✅ 用 ArrayDeque 作为队列
Deque<String> queue = new ArrayDeque<>();
queue.offer("first");
queue.offer("second");
String head = queue.poll(); // "first"

四、常见陷阱与注意事项

4.1 ConcurrentModificationException

// ❌ 错误:遍历时直接删除
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    if ("b".equals(s)) {
        list.remove(s); // 💥 ConcurrentModificationException
    }
}

// ✅ 方案一:Iterator 删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if ("b".equals(it.next())) {
        it.remove(); // 安全
    }
}

// ✅ 方案二:removeIf(JDK8+,推荐)
list.removeIf("b"::equals);

// ✅ 方案三:Stream 过滤生成新集合
list = list.stream()
           .filter(s -> !"b".equals(s))
           .collect(Collectors.toList());

4.2 Arrays.asList 的坑

// ⚠️ Arrays.asList 返回的是固定大小的 List,不能 add/remove
List<String> list = Arrays.asList("a", "b", "c");
list.add("d");    // 💥 UnsupportedOperationException
list.remove("a"); // 💥 UnsupportedOperationException
list.set(0, "x"); // ✅ 这个可以,因为只是替换元素

// ✅ 正确方式:包装成真正的 ArrayList
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ✅ JDK9+ 推荐:List.of(注意返回的是不可变集合)
List<String> immutable = List.of("a", "b", "c");
// immutable.add("d"); // 💥 也不行

// ✅ JDK9+ 可变集合
List<String> mutable = new ArrayList<>(List.of("a", "b", "c"));

4.3 subList 的内存泄漏

// ⚠️ subList 返回的是原 List 的视图,不是新 List
List<Integer> bigList = new ArrayList<>(IntStream.range(0, 1000000)
        .boxed().collect(Collectors.toList()));

// 这个 subList 持有对 bigList 的引用,bigList 无法被 GC
List<Integer> sub = bigList.subList(0, 10);

// ✅ 正确方式:拷贝一份
List<Integer> sub = new ArrayList<>(bigList.subList(0, 10));
// 现在 bigList 可以被 GC 了

4.4 HashMap 的 null 陷阱

// HashMap 允许 null key 和 null value
Map<String, String> map = new HashMap<>();
map.put(null, "value");  // ✅ 可以
map.put("key", null);    // ✅ 可以

// ConcurrentHashMap 不允许 null key 和 null value
Map<String, String> cmap = new ConcurrentHashMap<>();
cmap.put(null, "value"); // 💥 NullPointerException
cmap.put("key", null);   // 💥 NullPointerException

// ⚠️ TreeMap 不允许 null key(因为要比较大小),但允许 null value

4.5 equals 和 hashCode 的契约

// ⚠️ 如果你把自定义对象作为 HashMap 的 key 或放入 HashSet
// 必须同时重写 equals() 和 hashCode()

public class User {
    private Long id;
    private String name;

    // ❌ 只重写 equals 不重写 hashCode:
    // 两个相等的对象可能落在不同的 bucket,导致 HashMap 找不到

    // ✅ 正确做法:同时重写
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    // ✅ 实际项目中直接用 Lombok:@EqualsAndHashCode(of = "id")
}

4.6 集合判空的正确姿势

// ❌ 容易 NPE
if (list.size() == 0) { ... }
if (list.isEmpty()) { ... } // list 为 null 时一样 NPE

// ✅ 防御性判空
if (list == null || list.isEmpty()) { ... }

// ✅ 使用工具类(企业中最常见)
// Spring
if (CollectionUtils.isEmpty(list)) { ... }
// Apache Commons
if (CollectionUtils.isEmpty(list)) { ... }
// Hutool
if (CollUtil.isEmpty(list)) { ... }

4.7 返回空集合而非 null

// ❌ 返回 null,调用方必须判空
public List<User> findUsers() {
    if (noData) return null; // 调用方忘了判空就 NPE
}

// ✅ 返回空集合
public List<User> findUsers() {
    if (noData) return Collections.emptyList(); // 不可变空集合,不占内存
    // ...
}

// ✅ 返回空 Map
public Map<String, Object> getConfig() {
    if (noConfig) return Collections.emptyMap();
    // ...
}

五、集合与周边工具的配合使用

5.1 集合 + Stream API

// 实际业务中最常见的 Stream 操作组合
List<Order> orders = orderService.getOrders(userId);

// 过滤 + 排序 + 分页
List<OrderVO> result = orders.stream()
        .filter(o -> o.getStatus() != OrderStatus.CANCELLED)
        .sorted(Comparator.comparing(Order::getCreateTime).reversed())
        .skip(pageNum * pageSize)
        .limit(pageSize)
        .map(this::toOrderVO)
        .collect(Collectors.toList());

// 分组统计
Map<OrderStatus, Long> statusCount = orders.stream()
        .collect(Collectors.groupingBy(Order::getStatus, Collectors.counting()));

// 提取 ID 集合(超高频操作)
Set<Long> userIds = orders.stream()
        .map(Order::getUserId)
        .collect(Collectors.toSet());

// toMap(注意 key 冲突处理)
Map<Long, Order> orderMap = orders.stream()
        .collect(Collectors.toMap(
                Order::getId,
                Function.identity(),
                (v1, v2) -> v1  // ⚠️ key 冲突时保留第一个,不写这个参数 key 冲突会抛异常
        ));

5.2 集合 + Collections 工具类

// 不可变集合
List<String> immutable = Collections.unmodifiableList(list);
// JDK9+: List.of(), Map.of(), Set.of()
// JDK10+: List.copyOf(), Map.copyOf()

// 线程安全包装
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, Object> syncMap = Collections.synchronizedMap(new HashMap<>());
// ⚠️ 遍历时仍需手动加锁,高并发场景直接用 ConcurrentHashMap

// 排序
Collections.sort(list); // 自然排序
Collections.sort(list, Comparator.reverseOrder()); // 倒序
list.sort(Comparator.comparing(User::getAge)); // JDK8+ 推荐用 List.sort

// 二分查找(前提:list 已排序)
int index = Collections.binarySearch(list, target);

// 打乱顺序(洗牌)
Collections.shuffle(list);

// 填充
Collections.fill(list, defaultValue);

5.3 集合 + Guava(Google 工具库)

// ✅ 不可变集合(性能更优,编译期安全)
ImmutableList<String> list = ImmutableList.of("a", "b", "c");
ImmutableMap<String, Integer> map = ImmutableMap.of("a", 1, "b", 2);

// ✅ Multimap:一个 key 对应多个 value
ListMultimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("fruit", "apple");
multimap.put("fruit", "banana");
List<String> fruits = multimap.get("fruit"); // [apple, banana]
// 省去了 Map<String, List<String>> 的繁琐操作

// ✅ BiMap:双向映射
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("one", 1);
biMap.inverse().get(1); // "one"

// ✅ Table:二维 Map(行+列→值)
Table<String, String, Integer> table = HashBasedTable.create();
table.put("张三", "数学", 90);
table.put("张三", "英语", 85);
table.row("张三"); // {数学=90, 英语=85}

// ✅ Sets/Lists 工具
Sets.difference(set1, set2);    // 差集
Sets.intersection(set1, set2);  // 交集
Sets.union(set1, set2);         // 并集
Lists.partition(bigList, 100);  // 按 100 个一组分批

5.4 集合 + 序列化(JSON)

// Jackson(Spring Boot 默认)
ObjectMapper mapper = new ObjectMapper();

// 集合 → JSON
String json = mapper.writeValueAsString(userList);

// JSON → 集合(注意泛型擦除问题)
// ❌ 错误:泛型信息丢失,反序列化后元素是 LinkedHashMap
List<User> users = mapper.readValue(json, List.class);

// ✅ 正确:使用 TypeReference 保留泛型
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});

// ✅ 正确:使用 JavaType
JavaType type = mapper.getTypeFactory()
        .constructCollectionType(List.class, User.class);
List<User> users = mapper.readValue(json, type);

六、集合使用速查表

操作推荐写法说明
创建并初始化new ArrayList<>(List.of(1,2,3))JDK9+
预设容量new ArrayList<>(expectedSize)避免扩容
判空CollectionUtils.isEmpty(list)Spring 工具类
遍历删除list.removeIf(predicate)JDK8+
集合转数组list.toArray(new String[0])传空数组性能更优
数组转集合new ArrayList<>(Arrays.asList(arr))可变集合
Map 遍历map.forEach((k, v) -> ...)JDK8+
安全取值map.getOrDefault(key, defaultVal)避免 null
计算/缓存map.computeIfAbsent(key, k -> ...)惰性计算
合并值map.merge(key, val, Integer::sum)计数场景
不可变集合Collections.unmodifiableList(list)或 JDK9 List.of()
线程安全 MapConcurrentHashMap别用 Hashtable
线程安全 ListCopyOnWriteArrayList读多写少
new ArrayDeque<>()别用 Stack