【问题标题】:The correct way to return the only element from a set从集合中返回唯一元素的正确方法
【发布时间】:2011-07-10 21:23:27
【问题描述】:

我有以下几种情况:

Set<Element> set = getSetFromSomewhere();
if (set.size() == 1) {
    // return the only element
} else {
    throw new Exception("Something is not right..");
}

假设我无法更改 getSetFromSomewhere() 的返回类型,有没有比返回集合中唯一元素更好或更正确的方法

  • 遍历集合并立即返回
  • 从集合中创建一个列表并调用.get(0)

【问题讨论】:

  • 您可以调用 toArray() 方法然后访问第 0 个索引。但是,我认为这与“创建列表”非常相似
  • 好问题...没有好的答案

标签: java collections set


【解决方案1】:

你可以抓住迭代器:

Element firstEl = set.iterator().next();

【讨论】:

  • 这是我通常做的,但我将它包装在一个带有断言assert set.size() == 1 的方法中。
【解决方案2】:

最好的通用解决方案(你不知道实际的集合类)是:

Element first = set.iterator().next();

如果已知集合类是SortedSet(例如TreeSetConcurrentSkipListSet),那么更好的解决方案是:

Element first = ((SortedSet) set).first();

在这两种情况下,如果集合为空,都会抛出异常;检查javadocs。使用Collection.isEmpty() 可以避免该异常。


第一个解决方案是O(1) 在时间和空间上为HashSetLinkedHashSet,但对于其他类型的集合通常更糟。

第二个时间是O(logN)TreeSetConcurrentSkipListSet不占用空间。

从设置的内容创建一个列表然后调用List.get(0) 的方法是一个糟糕的解决方案,因为第一步是一个O(N) 操作,无论是在时间上还是空间上。


我没有注意到N 实际上是1。但即便如此,创建一个迭代器可能比创建一个临时列表更便宜。

【讨论】:

    【解决方案3】:

    您可以使用Iterator 来获取唯一元素以及验证集合是否仅包含一个元素(从而避免size() 调用和不必要的列表创建):

    Iterator<Element> iterator = set.iterator();
    
    if (!iterator.hasNext()) {
        throw new RuntimeException("Collection is empty");
    }
    
    Element element = iterator.next();
    
    if (iterator.hasNext()) {
        throw new RuntimeException("Collection contains more than one item");
    }
    
    return element;
    

    您通常会将其包装在自己的方法中:

    public static <E> E getOnlyElement(Iterable<E> iterable) {
        Iterator<E> iterator = iterable.iterator();
    
        // The code I mentioned above...
    }
    

    请注意,此实现已经是 Google 的 Guava libraries 的一部分(我强烈推荐,即使您不将它用于此特定代码)。更具体地说,该方法属于Iterables class

    Element element = Iterables.getOnlyElement(set);
    

    如果你好奇它是如何实现的,可以看Iterators class source codeIterables中的方法经常调用Iterators中的方法):

      /**
       * Returns the single element contained in {@code iterator}.
       *
       * @throws NoSuchElementException if the iterator is empty
       * @throws IllegalArgumentException if the iterator contains multiple
       *     elements.  The state of the iterator is unspecified.
       */
      public static <T> T getOnlyElement(Iterator<T> iterator) {
        T first = iterator.next();
        if (!iterator.hasNext()) {
          return first;
        }
    
        StringBuilder sb = new StringBuilder();
        sb.append("expected one element but was: <" + first);
        for (int i = 0; i < 4 && iterator.hasNext(); i++) {
          sb.append(", " + iterator.next());
        }
        if (iterator.hasNext()) {
          sb.append(", ...");
        }
        sb.append('>');
    
        throw new IllegalArgumentException(sb.toString());
      }
    

    【讨论】:

    • 你会特别想要Iterables.getOnlyElement()。这是番石榴中最流行的方法之一。我强烈推荐它。 :-)
    • @Kevin:我想我的回答不够清楚。我确实推荐了Iterables.getOnlyElement,但我通过Iterators.getOnlyElement 说明了它的行为(因为Iterables 调用Iterators)。
    【解决方案4】:

    在 Java 8 中,我们可以这样做:

    set.stream().findFirst().get()
    

    但是,记得在 Optional.get() throws NoSuchElementException 之前检查 set 的大小

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-22
      • 2018-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多