如何在Java中维护一个唯一列表?

128
如何在Java中创建一个独特/不重复的对象列表?目前我正在使用HashMap 来做这件事,因为键被覆盖,所以最后我们可以得到HashMap.getKeySet(),这将是唯一的。但我相信应该有更好的方法来做到这一点,因为这里浪费了值部分。
7个回答

203

你可以使用Set实现:

JAVADoc中的一些信息:

包含不同元素的集合。更正式地说,集合不包含元素e1和e2,使得e1.equals(e2),且至多一个null元素。正如其名称所暗示的那样,此接口模型化了数学集合抽象。

注意:如果将可变对象用作集合元素,则必须格外小心。如果在对象是集合中的元素时以影响等于比较的方式更改对象的值,则不指定集合的行为。该禁止的特殊情况是,集合不能包含自身作为元素。

以下是实现:

  • HashSet

    这个类对于基本操作(add、remove、contains和size)提供恒定时间性能,假设哈希函数正确地将元素分散在桶之间。遍历这个集合需要的时间与HashSet实例的大小(元素数)加上后备HashMap实例的“容量”(桶数)成比例。因此,如果迭代性能很重要,则不要将初始容量设置得太高(或负载因子太低)。

    当迭代HashSet时,产生的元素顺序是未定义的。

  • LinkedHashSet

    Set接口的哈希表和链接列表实现,具有可预测的迭代顺序。该实现不同于HashSet的地方在于它维护了通过其所有条目的双向链表。这个链接列表定义了迭代顺序,也就是元素插入到集合中的顺序(插入顺序)。请注意,如果将元素重新插入集合,则不会影响插入顺序。(如果在调用s.contains(e)将立即返回true之前调用s.add(e),则元素e被重新插入集合s中。)

    所以,上面代码的输出为...

 Set<Integer> linkedHashSet = new LinkedHashSet<>();
 linkedHashSet.add(3);
 linkedHashSet.add(1);
 linkedHashSet.add(2);

 for (int i : linkedHashSet) {
     System.out.println(i);
 }

一定会成为...

3
1
2
  • TreeSet

    这个实现提供了基本操作(添加、删除和包含)的对数时间复杂度。默认情况下,迭代返回的元素按其“自然顺序”排序,因此上面的代码...

  •  Set<Integer> treeSet = new TreeSet<>();
     treeSet.add(3);
     treeSet.add(1);
     treeSet.add(2);
    
     for (int i : treeSet) {
         System.out.println(i);
     }
    

    ...将会输出:

    1
    2
    3
    

    您也可以向 TreeSet 构造函数传递一个 Comparator 实例,以便按不同顺序对元素进行排序。

    请注意,由集合(无论是否提供显式比较器)维护的排序必须与相等关系一致,如果要正确实现 Set 接口,则必须如此。(有关一致的定义,请参见 Comparable 或 Comparator。)这是因为 Set 接口是基于 equals 操作定义的,但 TreeSet 实例使用其 compareTo(或 compare)方法执行所有元素比较,因此通过此方法被视为相等的两个元素,在集合的角度来看是相等的。即使集合的排序与 equals 不一致,其行为也是明确定义的;它只是未遵守 Set 接口的一般合同。


    现在我有点困惑,应该用哪个呢?我只需要维护一个唯一字符串列表。所以基本上即使添加了一个已存在的字符串,它也应该被添加进去。 - user1804287
    2
    选择在你手中... HashSet 是通用且快速的,TreeSet 是有序的,LinkedHashSet 保持插入顺序... - Frank
    12
    这不是一个列表...因此,并非所有列表接口方法都可用。 - marcolopes
    3
    集合不是列表,在集合中我无法通过索引以O(1)时间(随机访问)查找元素。 - wilmol
    几乎每个集合都在内部使用映射。因此,将映射更改为集合不会带来任何内存优势。 - Vitaliy Tsirkunov

    18
    我希望为原始发帖人澄清一些事情,其他人已经提到但没有明确说明的。当你说你想要一个唯一列表时,这就是有序集合的定义。Set 接口和 List 接口之间的一些关键区别是,List 允许您指定插入索引。所以问题是,你真的需要 List 接口(例如与第三方库兼容等)吗?还是可以重新设计软件以使用 Set 接口?你还必须考虑你正在使用接口来做什么。按索引查找元素很重要吗?你预计在集合中有多少元素?如果你将拥有许多元素,那么排序是否很重要?
    如果你真的需要一个仅具有唯一约束条件的 List,有一个 Apache Common Utils 类 org.apache.commons.collections.list.SetUniqueList 可以为你提供 List 接口和唯一约束。请注意,这会打破 List 接口。但是,如果你需要按索引查找列表中的元素,则会得到更好的性能。如果你可以处理 Set 接口,并且你有一个较小的数据集,则 LinkedHashSet 可能是一个好的选择。这只取决于你的软件的设计和意图。
    同样,每个集合都有优缺点。有些快速插入但读取较慢,有些快速读取但插入较慢,等等。花费相当一部分时间来研究集合文档,以充分了解每个类和接口的细节是有意义的。

    3
    这并不提供对问题的答案。如果你想要评论或请求作者进行澄清,请在他们的帖子下方留言——你可以对自己的帖子进行评论,并且一旦你获得足够的 声望,你就可以评论任何帖子了。 - Zach Saucier
    3
    实际上,它确实提供了一个答案。如果他只需要一个像Set一样的列表,请使用org.apache.commons.collections.list.SetUniqueList,但作为程序员,他/我们应该比这更加小心并且应该更多地思考问题。如果这使我的答案更好,“如何在Java中创建唯一列表?” List uniqueList = new SetUniqueList(); ,这就是方法... - Paul Connolly
    7
    Zach,我不是想表现得很坏,但你在评论之前有读过我的回答吗?还是你就是不理解?如果你不理解,没关系 - 让我知道,我会详细解释这个话题。我认为我不应该为了回答某人的问题而写一篇关于数据结构的论文。当我知道答案而其他人并没有提供时,我也不想以某种温和的方式建立我的评论声誉。 - Paul Connolly
    3
    顺便说一下,我既没有批评也没有向作者请求澄清,我只是在说他可以选择A)快速使用我给他的类,或者B)花时间真正理解这些类之间的差异并将它们与他的需求联系起来。B显然需要更长的时间,但从长远来看会产生更好的代码。 - Paul Connolly

    13

    使用new HashSet<String>

    import java.util.HashSet;
    import java.util.Set;
    
    public class MainClass {
      public static void main(String args[]) {
        String[] name1 = { "Amy", "Jose", "Jeremy", "Alice", "Patrick" };
    
        String[] name2 = { "Alan", "Amy", "Jeremy", "Helen", "Alexi" };
    
        String[] name3 = { "Adel", "Aaron", "Amy", "James", "Alice" };
    
        Set<String> letter = new HashSet<String>();
    
        for (int i = 0; i < name1.length; i++)
          letter.add(name1[i]);
    
        for (int j = 0; j < name2.length; j++)
          letter.add(name2[j]);
    
        for (int k = 0; k < name3.length; k++)
          letter.add(name3[k]);
    
        System.out.println(letter.size() + " letters must be sent to: " + letter);
    
      }
    }
    

    2
    只需将上述程序的输出添加--> 必须发送11个信件给:[Aaron,Alice,James,Adel,Jose,Jeremy,Amy,Alan,Patrick,Helen,Alexi] - Ammad

    6

    我不知道这有多有效,但在简单情境下对我起作用了。

    List<int> uniqueNumbers = new ArrayList<>();
    
       public void AddNumberToList(int num)
        {
            if(!uniqueNumbers .contains(num)) {
                uniqueNumbers .add(num);
            }
        }
    

    4
    你可以使用HashSet<String>来维护一个唯一对象的集合。如果你的映射中的Integer值很重要,那么你可以使用map的containsKey方法来测试你的键是否已经在map中存在。

    3

    HashSet<String>(或任何Set实现)都可以为您完成此任务。 Set不允许重复。

    这里是HashSet的javadoc文档。


    1

    您可能想使用java.util.Set<E>接口的一个实现类,例如java.util.HashSet<String>集合类。

    一个不包含重复元素的集合。更正式地说,集合不包含任何一对元素e1和e2,使得e1.equals(e2),并且最多只有一个null元素。正如其名称所暗示的那样,此接口模拟了数学集合抽象。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接