从集合中返回唯一元素的正确方法

2022-08-31 16:40:22

我有以下几种情况:

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

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

  • 迭代集合并立即返回
  • 从集合创建列表并调用.get(0)

答案 1

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

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库的一部分(我强烈建议您这样做,即使您不将其用于此特定代码)。更具体地说,该方法属于迭代类

Element element = Iterables.getOnlyElement(set);

如果你对它是如何实现的感到好奇,你可以看看迭代器类的源代码(方法中经常调用方法):IterablesIterators

  /**
   * 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());
  }

答案 2

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

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

如果集合类已知是 a(例如 a 或 ),则更好的解决方案是:SortedSetTreeSetConcurrentSkipListSet

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

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


第一个解是 a 或 的时间和空间,但对于其他类型的集合,通常更糟。O(1)HashSetLinkedHashSet

第二个是时间,并且不占用 或 的空间。O(logN)TreeSetConcurrentSkipListSet

从集合内容创建列表然后调用的方法给出了一个糟糕的解决方案,因为第一步是在时间和空间上都是一个操作。List.get(0)O(N)


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