2. 介绍

本文将探讨如何在Java的Set集合中获取元素的索引。Java中的Set不允许重复元素,常见实现包括HashSetTreeSetLinkedHashSet

3. Java中的有序、无序和有序集合

在解决问题前,先明确Java中三种集合类型的区别:

✅ 有序集合

  • 特点:保持元素插入顺序
  • 访问方式:可通过位置索引访问元素
  • 典型接口List(如ArrayListLinkedList
  • 关键方法:提供get(index)接口

❌ 无序集合

  • 特点:不保证遍历顺序
  • 存储机制:元素顺序由底层数据结构决定
  • 访问方式:通过值而非索引访问
  • 典型实现HashSetHashMap

⚠️ 有序集合(Sorted Collections)

  • 特点:按自然顺序或指定Comparator排序
  • 遍历结果:元素按排序顺序输出
  • 典型实现TreeSetTreeMap

4. 为什么Set不提供indexOf()方法

Java的Set作为无序集合,具有以下核心特性:

  • 保证元素唯一性
  • 支持常数时间复杂度的存在性检查

不同Set实现采用不同存储机制:

  • HashSet:基于哈希存储(内部使用HashMap
  • TreeSet:使用比较器排序元素

关键原因Set优先保证元素唯一性和存储效率,而非维护顺序。与List不同,在Set中获取元素索引并非简单操作。

5. 问题陈述

我们需要解决的核心问题:在给定Set中查找元素的索引。要求:

  • 索引值必须保持一致(多次查询结果相同)
  • 元素不存在时返回-1
示例1:
输入 Set [10, 2, 9, 15, 0]
查询: getIndexOf(10)
输出: 0
查询: getIndexOf(0)
输出: 4
示例2:
输入 Set ["Java", "Scala", "Python", "Ruby", "Go"]
查询: getIndexOf("Scala")
输出: 1

6. 编写获取索引的工具方法

6.1. 使用迭代器

通过Iterator遍历集合,记录目标元素的位置:

public int getIndexUsingIterator(Set<E> set, E element) {
    Iterator<E> iterator = set.iterator();
    int index = 0;
    while (iterator.hasNext()) {
        if (element.equals(iterator.next())) {
            return index;
        }
        index++;
    }
    return -1;
}

6.2. 使用For-Each循环

更简洁的实现方式:

public int getIndexUsingForEach(Set<E> set, E element) {
    int index = 0;
    for (E current : set) {
        if (element.equals(current)) {
            return index;
        }
        index++;
    }
    return -1;
}

性能说明

  • 时间复杂度:O(n)(n为集合大小)
  • 空间复杂度:O(1)
  • 多次调用结果一致,验证了正确性

6.3. 在不同类型的Set上应用实现

⚠️ 重要提醒:迭代器遍历顺序可能与插入顺序不同,尤其在使用HashSet时:

Set<Integer> set = new HashSet<>();
set.add(100);
set.add(20);
// 添加更多元素
Assert.assertEquals(2, integerIndexOfElementsInSet.getIndexUsingIterator(set,100));

尽管100是第一个插入的元素,但返回索引为2。原因HashSet的迭代顺序由哈希值决定,而非插入顺序。

解决方案:使用LinkedHashSet维护插入顺序:

Set<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(20);
// 添加更多元素
Assert.assertEquals(0, integerIndexOfElementsInSet.getIndexUsingIterator(set, 100));

LinkedHashSet内部使用LinkedList,能保持元素插入顺序。

对于TreeSet,索引基于元素的自然排序:

Set<Integer> set = new TreeSet<>();
set.add(0);
set.add(-1);
set.add(100);
// 添加更多元素
Assert.assertEquals(0, integerIndexOfElementsInSet.getIndexUsingIterator(set, -1));
Assert.assertEquals(3, integerIndexOfElementsInSet.getIndexUsingIterator(set, 100));

7. 编写自定义的LinkedHashSet实现

虽然不推荐仅为添加一个方法而创建子类,但我们可以扩展LinkedHashSet

public class InsertionIndexAwareSet<E> extends LinkedHashSet<E> {
    
    public int getIndexOf(E element) {
        int index = 0;
        for (E current : this) {
            if (current.equals(element)) {
                return index;
            }
            index++;
        }
        return -1;
    }
}

使用示例:

@Test
public void givenIndexAwareSetWithStrings_whenIndexOfElement_thenGivesIndex() {
    InsertionIndexAwareSet<String> set = new InsertionIndexAwareSet<>();
    set.add("Go");
    set.add("Java");
    set.add("Scala");
    set.add("Python");
    Assert.assertEquals(0, set.getIndexOf("Go"));
    Assert.assertEquals(2, set.getIndexOf("Scala"));
    Assert.assertEquals(-1, set.getIndexOf("C++"));
}

8. 使用Apache Commons Collections

借助第三方库简化实现。首先添加Maven依赖:

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

使用ListOrderedSet(装饰器模式实现,维护插入顺序):

@Test
public void givenListOrderedSet_whenIndexOfElement_thenGivesIndex() {
    ListOrderedSet<Integer> set = new ListOrderedSet<>();
    set.add(12);
    set.add(0);
    set.add(-1);
    set.add(50);
    Assert.assertEquals(0, set.indexOf(12));
    Assert.assertEquals(2, set.indexOf(-1));
}

特性:添加重复元素时,元素保留原始位置。

9. 结论

本文探讨了在Java Set中获取元素索引的多种方法:

  • 基础实现:迭代器/For-Each循环(O(n)时间复杂度)
  • 顺序保证:使用LinkedHashSet维护插入顺序
  • 自定义实现:扩展LinkedHashSet添加索引方法
  • 第三方方案:Apache Commons Collections的ListOrderedSet

关键点

  • HashSet不保证顺序,索引结果可能不符合预期
  • LinkedHashSet是维护插入顺序的最佳选择
  • TreeSet按排序顺序提供索引

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


原始标题:How to Get Index of an Item in Java Set