Apache Commons Collections 快速入门与集成示例
commons-collections4 4.6.0 快速入门:HashBag 计数、BidiMap 双向、MapIterator、谓词变换、MultiValuedMap、集合运算、PatriciaTrie 前缀检索七组示例,全部实测。
Apache Commons Collections 快速入门与集成示例
目标:用七组可独立编译运行的示例快速掌握 commons-collections4 4.6.0 的核心 API——Bag 计数集合、BidiMap 双向映射、MapIterator/OrderedMap、谓词过滤与变换器、MultiValuedMap 一键多值、集合交并差运算、PatriciaTrie 前缀检索,并给出 Maven 集成与错误排查。已在 Ubuntu 22.04.5 LTS(x86_64、OpenJDK 17.0.20.1)实测通过,CentOS 8(OpenJDK 17.0.1 与 11.0.13)同口径复测结果一致。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1(示例在 JDK 11 亦复测通过)
- Maven:3.9.16
- 目标库:commons-collections4-4.6.0.jar(Maven 坐标 org.apache.commons:commons-collections4:4.6.0)
- 工作目录:/root/collections-lab(manual 为手动 jar 方式,examples 为示例源码目录)
Commons Collections 定位于集合扩展库:在 JDK 集合框架之上补充计数、双向、多值、前缀树等结构与函数式工具,全部示例只依赖这一个 jar。
💡 技术拓展:同样是集合扩展能力,几大主流库的取舍对比——
库 定位/特点 适用场景 JDK 内置 java.util 标准集合,稳定通用 常规业务,无额外结构需求 Apache Commons Collections Bag/BidiMap/MultiValuedMap/Trie/谓词装饰器一应俱全 需要 JDK 缺失的集合结构 Guava Multiset/Multimap/Range/Table,与 Google 生态绑定紧密 项目已引入 Guava Eclipse Collections 高性能与原始类型集合,API 风格独立 极致性能与内存敏感场景 本文选用 Apache Commons Collections 4.6.0:单 jar 零依赖、结构覆盖最全,且本篇七组示例全部实测跑通。
二、五分钟接入
2.1 准备 jar(两种方式任选其一)
方式一:手动下载官方 bin 包(sha512 校验值 b54c6173 开头、d3aa5 结尾):
mkdir -p /root/collections-lab/manual && cd /root/collections-lab/manual
curl -sSOL https://downloads.apache.org/commons/collections/binaries/commons-collections4-4.6.0-bin.tar.gz
echo "b54c61737ff2ec7eaf6e1caf4d2adae42534dd28a9383c3fae1d5f299bce97b00ca15aee2c81ba4a4b50e64db69103cc3629a43af39b52f90990cdee944d3aa5 commons-collections4-4.6.0-bin.tar.gz" | sha512sum -c -
tar xzf commons-collections4-4.6.0-bin.tar.gz
实测回显:
commons-collections4-4.6.0-bin.tar.gz: OK
方式二:Maven 工程在 pom.xml 声明依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.6.0</version>
</dependency>
坐标解析实测(mvn dependency:list | grep collections4):
[INFO] org.apache.commons:commons-collections4:jar:4.6.0:compile -- module org.apache.commons.collections4
后文统一用变量 J 指代 jar 路径,编译运行模板为:
J=/root/collections-lab/manual/commons-collections4-4.6.0/commons-collections4-4.6.0.jar
javac -encoding UTF-8 -cp $J 示例.java
java -cp .:$J 示例
2.2 第一个程序
最小验证程序 HelloCollections.java(手动方式演示,Maven 方式把类放进 src/main/java 后用 mvn compile 同样可跑):
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;
public class HelloCollections {
public static void main(String[] args) {
Bag<String> bag = new HashBag<>();
bag.add("apple", 3);
bag.add("banana");
System.out.println("Hello commons-collections4! bag = " + bag);
}
}
编译运行与实测回显:
javac -cp $J HelloCollections.java
java -cp .:$J HelloCollections
Hello commons-collections4! bag = [1:banana,3:apple]
输出 [1:banana,3:apple] 即 Bag 的 元素:计数 格式,说明库已可用。
三、核心 API 七组示例
七组示例相互独立,各自编译运行即可。每个示例后紧跟真实回显。
3.1 HashBag:计数集合
元素可重复出现,核心是「次数」而非「是否包含」(BagDemo.java):
import org.apache.commons.collections4.Bag;
import org.apache.commons.collections4.bag.HashBag;
public class BagDemo {
public static void main(String[] args) {
Bag<String> bag = new HashBag<>();
bag.add("apple", 3);
bag.add("banana");
bag.add("apple");
System.out.println("bag = " + bag);
System.out.println("getCount(apple) = " + bag.getCount("apple"));
System.out.println("getCount(banana) = " + bag.getCount("banana"));
System.out.println("getCount(cherry) = " + bag.getCount("cherry"));
System.out.println("uniqueSet = " + bag.uniqueSet());
System.out.println("size = " + bag.size());
bag.remove("apple", 2);
System.out.println("after remove(apple,2): getCount(apple) = " + bag.getCount("apple") + ", size = " + bag.size());
}
}
实测回显:
bag = [1:banana,4:apple]
getCount(apple) = 4
getCount(banana) = 1
getCount(cherry) = 0
uniqueSet = [banana, apple]
size = 5
after remove(apple,2): getCount(apple) = 2, size = 3
解说:add(e,n) 一次加 n 个;getCount 对不存在的元素返回 0 不抛异常;size 是全部元素按计数展开的总个数(4+1=5);uniqueSet 是去重后的元素视图。4.6.0 起 Bag/HashBag 被标记过时,等价替代为 MultiSet/HashMultiSet,计数语义不变(MultiSetDemo 实测输出 ms = [banana:1, apple:3]),老代码可平滑迁移。
3.2 BidiMap:双向映射
键找值与值找键双向 O(1)(BidiMapDemo.java,实现选 DualTreeBidiMap):
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualTreeBidiMap;
public class BidiMapDemo {
public static void main(String[] args) {
BidiMap<String, Integer> map = new DualTreeBidiMap<>();
map.put("alice", 1001);
map.put("bob", 1002);
map.put("carol", 1003);
System.out.println("map = " + map);
System.out.println("map.get(bob) = " + map.get("bob"));
System.out.println("map.getKey(1002) = " + map.getKey(1002));
BidiMap<Integer, String> inverse = map.inverseBidiMap();
System.out.println("inverse = " + inverse);
System.out.println("inverse.getKey(bob) = " + inverse.getKey("bob"));
}
}
实测回显:
map = {alice=1001, bob=1002, carol=1003}
map.get(bob) = 1002
map.getKey(1002) = bob
inverse = {1001=alice, 1002=bob, 1003=carol}
inverse.getKey(bob) = 1002
解说:getKey(value) 是反向查找;inverseBidiMap 返回键值互换的视图且与原表联动。实现族按需选择:DualHashBidiMap(哈希无序)、DualTreeBidiMap(键序,本例)、DualLinkedHashBidiMap(插入序)、TreeBidiMap(红黑树双向有序)。注意值必须唯一,重复 put 同值会替换旧键映射。
3.3 MapIterator 与 OrderedMap
MapIterator 把「遍历键 + 取值 + 改值」合成一个游标(MapIteratorDemo.java,用 LinkedMap 保插入序):
import org.apache.commons.collections4.MapIterator;
import org.apache.commons.collections4.OrderedMap;
import org.apache.commons.collections4.map.LinkedMap;
public class MapIteratorDemo {
public static void main(String[] args) {
OrderedMap<String, Integer> map = new LinkedMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
MapIterator<String, Integer> it = map.mapIterator();
while (it.hasNext()) {
String key = it.next();
System.out.println("visit " + key + " -> " + it.getValue());
it.setValue(it.getValue() * 10);
}
System.out.println("after setValue x10: map = " + map);
System.out.println("firstKey = " + map.firstKey() + ", nextKey(one) = " + map.nextKey("one"));
System.out.println("lastKey = " + map.lastKey() + ", previousKey(three) = " + map.previousKey("three"));
}
}
实测回显:
visit one -> 1
visit two -> 2
visit three -> 3
after setValue x10: map = {one=10, two=20, three=30}
firstKey = one, nextKey(one) = two
lastKey = three, previousKey(three) = two
解说:it.next() 返回键、it.getValue()/setValue() 操作当前值,省去 Map.Entry 对象;LinkedMap 额外提供 firstKey/lastKey/nextKey/previousKey 双向游标遍历,这是 JDK LinkedHashMap 没有的 API。
3.4 Predicate 谓词过滤与 Transformer 变换
CollectionUtils.filter 原地过滤,PredicateUtils 组合条件;ChainedTransformer 把多个变换串成管道(PredicateTransformerDemo.java):
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.PredicateUtils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
public class PredicateTransformerDemo {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
nums.add(i);
}
Predicate<Integer> even = n -> n % 2 == 0;
Predicate<Integer> gt4 = n -> n > 4;
Predicate<Integer> evenAndGt4 = PredicateUtils.andPredicate(even, gt4);
List<Integer> copy = new ArrayList<>(nums);
CollectionUtils.filter(copy, evenAndGt4);
System.out.println("filter(even & gt4): copy = " + copy);
List<Integer> copy2 = new ArrayList<>(nums);
CollectionUtils.filterInverse(copy2, evenAndGt4);
System.out.println("filterInverse(even & gt4): copy2 = " + copy2);
Transformer<Integer, Integer> square = n -> n * n;
Transformer<Integer, Integer> plus100 = n -> n + 100;
Transformer<Integer, Integer> pipeline = new ChainedTransformer<>(square, plus100);
Collection<Integer> squared = CollectionUtils.collect(nums, pipeline);
System.out.println("collect(square then plus100): squared = " + squared);
Transformer<Integer, String> decorate = n -> "#" + n;
Collection<String> named = CollectionUtils.collect(squared, decorate);
System.out.println("collect(decorate): named = " + named);
}
}
实测回显:
filter(even & gt4): copy = [6, 8, 10]
filterInverse(even & gt4): copy2 = [1, 2, 3, 4, 5, 7, 9]
collect(square then plus100): squared = [101, 104, 109, 116, 125, 136, 149, 164, 181, 200]
collect(decorate): named = [#101, #104, #109, #116, #125, #136, #149, #164, #181, #200]
解说:filter 保留谓词为真的元素(原地删除其余),filterInverse 反向保留;andPredicate 组合两个条件(orPredicate/notPredicate 同族);ChainedTransformer 按序执行且类型必须 T→T 一致(square 后 plus100),跨类型变换用两次 collect 衔接(squared 再 decorate);collect 即映射转换,把 1..10 变成 101..200 再格式化。
3.5 MultiValuedMap:一键多值
JDK Map 一个键只能一个值,MultiValuedMap 直接支持(MultiValuedMapDemo.java):
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
public class MultiValuedMapDemo {
public static void main(String[] args) {
MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
map.put("fruit", "apple");
map.put("fruit", "banana");
map.put("veggie", "carrot");
System.out.println("map = " + map);
System.out.println("get(fruit) = " + map.get("fruit"));
System.out.println("get(meat) = " + map.get("meat"));
System.out.println("containsMapping(fruit, apple) = " + map.containsMapping("fruit", "apple"));
System.out.println("size = " + map.size() + ", keys = " + map.asMap().keySet());
map.removeMapping("fruit", "banana");
System.out.println("after removeMapping(fruit, banana): get(fruit) = " + map.get("fruit"));
}
}
实测回显:
map = {veggie=[carrot], fruit=[apple, banana]}
get(fruit) = [apple, banana]
get(meat) = []
containsMapping(fruit, apple) = true
size = 3, keys = [veggie, fruit]
after removeMapping(fruit, banana): get(fruit) = [apple]
解说:size 计「键值对总数」(3 个),asMap().keySet() 看键集合;get 永不返回 null;removeMapping 精确删除一个键值对。内层集合实现按需选:ArrayListValuedHashMap 保序可重复,HashSetValuedHashMap 去重(实测重复 put 同键同值只留一份)。
3.6 集合运算:union/intersection/disjunction
以「出现次数」为语义的集合代数(SetOpsDemo.java):
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.collections4.CollectionUtils;
public class SetOpsDemo {
public static void main(String[] args) {
Collection<Integer> a = Arrays.asList(1, 2, 3, 4);
Collection<Integer> b = Arrays.asList(3, 4, 5, 6);
System.out.println("a = " + a + ", b = " + b);
System.out.println("union(a, b) = " + CollectionUtils.union(a, b));
System.out.println("intersection(a, b) = " + CollectionUtils.intersection(a, b));
System.out.println("disjunction(a, b) = " + CollectionUtils.disjunction(a, b));
System.out.println("subtract(a, b) = " + CollectionUtils.subtract(a, b));
System.out.println("containsAny(a, b) = " + CollectionUtils.containsAny(a, b));
}
}
实测回显:
a = [1, 2, 3, 4], b = [3, 4, 5, 6]
union(a, b) = [1, 2, 3, 4, 5, 6]
intersection(a, b) = [3, 4]
disjunction(a, b) = [1, 2, 5, 6]
subtract(a, b) = [1, 2]
containsAny(a, b) = true
解说:union 并集、intersection 交集、disjunction 只属于一方的元素(对称差)、subtract 差集 a-b、containsAny 快速判交。这组 API 底层按 bag 计数实现,元素重复时按次数取 max/min,比手写 Set 拷贝更省心。
3.7 PatriciaTrie:前缀检索
按字符串公共前缀取子映射(TrieDemo.java):
import org.apache.commons.collections4.Trie;
import org.apache.commons.collections4.trie.PatriciaTrie;
public class TrieDemo {
public static void main(String[] args) {
Trie<String, Integer> trie = new PatriciaTrie<>();
trie.put("collection", 1);
trie.put("collections", 2);
trie.put("collector", 3);
trie.put("bicycle", 4);
System.out.println("trie = " + trie);
System.out.println("prefixMap(col) = " + trie.prefixMap("col"));
System.out.println("prefixMap(coll) = " + trie.prefixMap("coll"));
System.out.println("prefixMap(bi) = " + trie.prefixMap("bi"));
System.out.println("firstKey = " + trie.firstKey() + ", lastKey = " + trie.lastKey());
}
}
实测输出(trie 的打印为树结构全展开,prefixMap 各行完整):
trie = Trie[4]={
Entry(key=bicycle [16], value=4, parent=collection [0], left=bicycle [16], right=collector [133], predecessor=bicycle [16])
Entry(key=collection [0], value=1, parent=ROOT, left=ROOT, right=bicycle [16], predecessor=collections [170])
Entry(key=collections [170], value=2, parent=collector [133], left=collection [0], right=collections [170], predecessor=collections [170])
Entry(key=collector [133], value=3, parent=bicycle [16], left=collections [170], right=collector [133], predecessor=collector [133])
}
prefixMap(col) = {collection=1, collections=2, collector=3}
prefixMap(coll) = {collection=1, collections=2, collector=3}
prefixMap(bi) = {bicycle=4}
firstKey = bicycle, lastKey = collector
解说:prefixMap 返回所有以前缀开头的键的 SortedMap 视图,代价 O(前缀长 + 命中数),是自动补全、标签联想类功能的现成底座;PatriciaTrie 的键类型固定为 String(泛型只有 V),firstKey/lastKey 按字典序。
四、集成到现有 Maven 工程
把上述示例挪进标准 Maven 工程只需三步:pom 加依赖坐标(2.1 方式二)、源码放 src/main/java、mvn compile。完整工程结构实测:
collections-demo/
pom.xml
src/main/java/com/example/CollectionDemo.java
编译与依赖落地:
cd /root/collections-lab/maven-demo
mvn compile
mvn dependency:copy-dependencies -DoutputDirectory=target/dep
ls target/dep/
实测输出节选(compile 的 BUILD SUCCESS 段与 dep 目录):
[INFO] BUILD SUCCESS
[INFO] Total time: 0.683 s
[INFO] Finished at: 2026-10-13T04:59:43Z
commons-collections4-4.6.0.jar
用 java -cp 运行(Spring Boot 等框架工程同样适用,把依赖交给框架插件打包即可):
java -cp target/classes:target/dep/\* com.example.CollectionDemo
实测回显:
collections4 works! bag = [1:banana,3:apple]
getCount(apple) = 3
集成注意:pom 里 maven.compiler.source/target 建议设 8(与本库最低要求一致),产物可在 JDK 8/11/17 通用;本库无传递依赖,dependency:copy-dependencies 只会复制它自己一个 jar。
五、常见错误与排查
以下四条全部实测命中过,按现象给处置。
错误一: UnsupportedClassVersionError,运行报「class file version 61.0, this version only recognizes up to 55.0」。JDK 17 编译的 class 被 JDK 11 运行导致(实测发生在 CentOS 8 双版本切换后)。处置:同一 JDK 编译运行,或 pom 固定 maven.compiler.source/target=8;用 javap -v 类名.class | grep major 核对产物版本(实测 61 对应 17、55 对应 11)。
错误二: javac 报「error: unmappable character (0xE5) for encoding US-ASCII」。无 LANG 环境下 javac 默认按 US-ASCII 读含中文的源码(实测命中)。处置:javac 加 -encoding UTF-8。
错误三: 编译报「cannot infer type arguments for ChainedTransformer」。该类泛型是 Transformer<T,T>,要求链上所有变换同类型,把 Integer→String 直接接在 Integer→Integer 后面无法通过。处置:同类型用链,跨类型分两步 collect(3.4 实测示例即此写法)。
错误四: jdeps 报「is a multi-release jar file but --multi-release option is not set」。4.6.0 是 multi-release jar(内含 META-INF/versions/9/module-info.class),jdeps 必须加 --multi-release 17(或目标版本)再执行。
六、参考链接
- 官方用户指南:https://commons.apache.org/proper/commons-collections/userguide/index.html
- 4.6.0 Javadoc:https://commons.apache.org/proper/commons-collections/javadocs/api-4.6.0/
- 官方发行包目录:https://downloads.apache.org/commons/collections/
- Maven 中央仓库:https://mvnrepository.com/artifact/org.apache.commons/commons-collections4
- 项目源码仓库:https://github.com/apache/commons-collections