1. 简介

本文将深入探讨 Apache Commons Collections 库中的 Bag 接口及其使用场景。Bag 是一种特殊的集合类型,允许存储重复元素并跟踪每个元素的出现次数。

2. Maven 依赖

使用前需添加 Maven 依赖(最新版本可从 Maven Central 获取):

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.1</version>
</dependency>

3. Bag 与标准集合的区别

简单来说,Bag 是一个能存储元素及其重复计数的集合:

public void whenAdded_thenCountIsKept() {
    Bag<Integer> bag = new HashBag<>(
      Arrays.asList(1, 2, 3, 3, 3, 1, 4));
        
    assertThat(2, equalTo(bag.getCount(1)));
}

3.1. 违反集合契约的行为 ⚠️

Bag 的某些方法会违反 Java 集合标准契约。例如:

  • 标准集合:添加已存在元素返回 true

    Collection<Integer> collection = new ArrayList<>();
    collection.add(1);
    assertThat(collection.add(1), is(true));
    
  • Bag:添加已存在元素返回 false

    Bag<Integer> bag = new HashBag<>();
    bag.add(1);
    assertThat(bag.add(1), is(not(true)));
    

解决方案:使用 CollectionBag 装饰器使其符合集合契约:

public void whenBagAddAPILikeCollectionAPI_thenTrue() {
    Bag<Integer> bag = CollectionBag.collectionBag(new HashBag<>());
    bag.add(1);
    assertThat(bag.add(1), is((true)));
}

4. Bag 的实现类

4.1. HashBag

核心特性

  • 支持批量添加指定数量的元素
  • 可精确删除指定数量的副本
public void givenAdd_whenCountOfElementsDefined_thenCountAreAdded() {
    Bag<Integer> bag = new HashBag<>();
    bag.add(1, 5); // 添加5个1
    assertThat(5, equalTo(bag.getCount(1)));
}
public void givenMultipleCopies_whenRemove_allAreRemoved() {
    Bag<Integer> bag = new HashBag<>(
      Arrays.asList(1, 2, 3, 3, 3, 1, 4));

    bag.remove(3, 1); // 删除1个3,剩余2个
    assertThat(2, equalTo(bag.getCount(3)));
    
    bag.remove(1); // 删除所有1
    assertThat(0, equalTo(bag.getCount(1)));
}

4.2. TreeBag

核心特性

  • 自动排序(基于元素自然顺序或自定义比较器)
  • 实现 SortedBag 接口
public void givenTree_whenDuplicateElementsAdded_thenSort() {
    TreeBag<Integer> bag = new TreeBag<>(Arrays.asList(7, 5,
      1, 7, 2, 3, 3, 3, 1, 4, 7));
    
    assertThat(bag.first(), equalTo(1)); // 最小值
    assertThat(bag.getCount(bag.first()), equalTo(2)); // 1出现2次
    assertThat(bag.last(), equalTo(7)); // 最大值
    assertThat(bag.getCount(bag.last()), equalTo(3)); // 7出现3次
}

契约兼容方案:使用 CollectionSortedBag 装饰器:

public void whenTreeAddAPILikeCollectionAPI_thenTrue() {
    SortedBag<Integer> bag 
      = CollectionSortedBag.collectionSortedBag(new TreeBag<>());
    bag.add(1);
    assertThat(bag.add(1), is((true)));
}

4.3. SynchronizedSortedBag

核心特性

  • 线程安全的 SortedBag 装饰器
  • 适用于并发场景
public void givenSortedBag_whenDuplicateElementsAdded_thenSort() {
    SynchronizedSortedBag<Integer> bag = SynchronizedSortedBag
      .synchronizedSortedBag(new TreeBag<>(
        Arrays.asList(7, 5, 1, 7, 2, 3, 3, 3, 1, 4, 7)));
    
    assertThat(bag.first(), equalTo(1));
    assertThat(bag.getCount(bag.first()), equalTo(2));
    assertThat(bag.last(), equalTo(7));
    assertThat(bag.getCount(bag.last()), equalTo(3));
}

💡 替代方案:也可组合使用 Collections.synchronizedSortedMap()TreeMap 实现类似功能,但 SynchronizedSortedBag 更简单直接。

5. 总结

本文系统介绍了 Apache Commons Collections 中 Bag 接口的核心实现:

  • HashBag:基础实现,适合无序场景
  • TreeBag:有序实现,支持自动排序
  • SynchronizedSortedBag:线程安全实现

所有示例代码可在 GitHub 获取。


原始标题:Apache Commons Collections Bag