2. Maven 依赖配置

首先需要在 pom.xml 中添加 Maven 依赖:

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

✅ 最新版本可在 Maven 中央仓库 查询


3. OrderedMap 核心特性

实现了 OrderedMap 接口的映射容器具有以下关键特性:

  • 保持键的插入顺序(注意:不是排序顺序)
  • 支持双向遍历
    • 正向:firstKey() + nextKey()
    • 反向:lastKey() + previousKey()
  • 专用迭代器:可通过 MapIterator 遍历
  • 增强操作方法:支持查找/修改/删除/替换元素

⚠️ 特别注意:键集(key set)保持的是插入顺序而非自然排序顺序


4. OrderedMap 实战演示

我们使用测试类演示 LinkedMapOrderedMap 的实现类)的用法。先准备数据:

public class OrderMapUnitTest {
    private String[] names = {"Emily", "Mathew", "Rose", "John", "Anna"};
    private Integer[] ages = {37, 28, 40, 36, 21};
    private LinkedMap<String, Integer> runnersLinkedMap;
 
    //...
}

初始化映射:

@Before
public void createRunners() {
    this.runnersLinkedMap = new LinkedMap<>();
    
    for (int i = 0; i < RUNNERS_COUNT; i++) {
        runners.put(this.names[i], this.ages[i]);
    }
}

4.1 正向遍历

使用正向迭代器遍历:

@Test
public void givenALinkedMap_whenIteratedForwards_thenPreservesOrder() {
    String name = this.runnersLinkedMap.firstKey();
    int i = 0;
    while (name != null) {
        assertEquals(name, names[i]);
        name = this.runnersLinkedMap.nextKey(name);
        i++;
    }
}

⚠️ 当到达最后一个键时,nextKey() 返回 null

4.2 反向遍历

从最后一个键开始反向遍历:

@Test
public void givenALinkedMap_whenIteratedBackwards_thenPreservesOrder() {
    String name = this.runnersLinkedMap.lastKey();
    int i = RUNNERS_COUNT - 1;
    while (name != null) {
        assertEquals(name, this.names[i]);
        name = this.runnersLinkedMap.previousKey(name);
        i--;
    }
}

⚠️ 到达第一个键时,previousKey() 返回 null

4.3 MapIterator 使用示例

通过 mapIterator() 获取专用迭代器:

@Test
public void givenALinkedMap_whenIteratedWithMapIterator_thenPreservesOrder() {
    OrderedMapIterator<String, Integer> runnersIterator 
      = this.runnersLinkedMap.mapIterator();
    
    int i = 0;
    while (runnersIterator.hasNext()) {
        runnersIterator.next();
 
        assertEquals(runnersIterator.getKey(), this.names[i]);
        assertEquals(runnersIterator.getValue(), this.ages[i]);
        i++;
    }
}

✅ 迭代器严格保持键值对的插入顺序

4.4 元素移除操作

支持通过键或索引移除元素:

@Test
public void givenALinkedMap_whenElementRemoved_thenSizeDecrease() {
    LinkedMap<String, Integer> lmap 
      = (LinkedMap<String, Integer>) this.runnersLinkedMap;
    
    Integer johnAge = lmap.remove("John");  // 按键移除
 
    assertEquals(johnAge, new Integer(36));
    assertEquals(lmap.size(), RUNNERS_COUNT - 1);

    Integer emilyAge = lmap.remove(0);     // 按索引移除
 
    assertEquals(emilyAge, new Integer(37));
    assertEquals(lmap.size(), RUNNERS_COUNT - 2);
}

5. 核心实现类

当前版本(4.1)提供两个实现类:

ListOrderedMap

  • 内部使用 java.util.List 维护键顺序
  • 装饰器模式实现,通过静态方法创建:
    ListOrderedMap.decorate(Map map)
    

LinkedMap

  • 基于 HashMap 扩展
  • 原生支持双向迭代等 OrderedMap 特性

增强方法(超出 OrderedMap 接口)

两个实现类均提供以下实用方法:

方法 说明
asList() 获取有序的键列表 List<K>
get(int index) 按索引获取元素(区别于 get(Object o)
indexOf(Object o) 获取对象在有序映射中的索引

asList() 使用示例

@Test
public void givenALinkedMap_whenConvertedToList_thenMatchesKeySet() {
    LinkedMap<String, Integer> lmap 
      = (LinkedMap<String, Integer>) this.runnersLinkedMap;
    
    List<String> listKeys = new ArrayList<>();
    listKeys.addAll(this.runnersLinkedMap.keySet());
    List<String> linkedMap = lmap.asList();
 
    assertEquals(listKeys, linkedMap);
}

索引操作示例

@Test
public void givenALinkedMap_whenSearchByIndexIsUsed_thenMatchesConstantArray() {
    LinkedMap<String, Integer> lmap 
      = (LinkedMap<String, Integer>) this.runnersLinkedMap;
    
    for (int i = 0; i < RUNNERS_COUNT; i++) {
        String name = lmap.get(i);  // 按索引获取
 
        assertEquals(name, this.names[i]);
        assertEquals(lmap.indexOf(this.names[i]), i);  // 验证索引
    }
}

6. 总结

本文系统梳理了 OrderedMap 接口的核心特性与实现方案:

  1. 核心价值:在保留 Map 功能的同时,提供可预测的键顺序
  2. 遍历能力:支持正向/反向迭代及专用 MapIterator
  3. 实现选择
    • LinkedMap:轻量级原生实现
    • ListOrderedMap:装饰器模式实现
  4. 增强操作:索引访问和列表转换等实用方法

📖 深入参考:Apache Commons Collections 官方文档
💻 完整示例代码:GitHub 项目


原始标题:Apache Commons Collections OrderedMap