Правильный способ вернуть единственный элемент из набора - PullRequest
41 голосов
/ 08 марта 2011

У меня следующая ситуация:

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

Если я не могу изменить тип возврата getSetFromSomewhere(), есть ли лучший или более правильный способ вернуть единственный элемент в наборе, чем

  • Перебор набора и немедленный возврат
  • Создание списка из набора и вызов .get(0)

Ответы [ 4 ]

46 голосов
/ 08 марта 2011

Вы можете использовать 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 (которые я настоятельно рекомендую , даже если вы не используете ее для этого конкретного кода). Более конкретно, этот метод относится к Iterables классу :

Element element = Iterables.getOnlyElement(set);

Если вам интересно, как это реализовано, вы можете взглянуть на исходный код класса Iterators (методы в Iterables часто вызывают методы в 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());
  }
17 голосов
/ 08 марта 2011

Лучшее общее решение (где вы не знаете фактический класс набора):

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

Если известно, что установленный класс равен SortedSet (например, TreeSet или ConcurrentSkipListSet), то лучшим решением будет:

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

В обоих случаях будет выдано исключение, если набор пуст; проверьте Javadocs. Исключения можно избежать, используя Collection.isEmpty().


Первое решение - O(1) во времени и пространстве для HashSet или LinkedHashSet, но обычно хуже для других видов сетов.

Второй - O(logN) во времени и не использует места для TreeSet или ConcurrentSkipListSet.

Подход к созданию списка из заданного содержимого и последующему вызову List.get(0) дает плохое решение, поскольку первым шагом является операция O(N), как во времени, так и в пространстве.


Я не заметил, что N на самом деле 1. Но даже в этом случае создание итератора, вероятно, будет дешевле, чем создание временного списка.

9 голосов
/ 08 марта 2011

Вы можете получить итератор:

Element firstEl = set.iterator().next();
0 голосов
/ 26 июня 2013
if(set.size()==1){
   set.toArray(new Element[0])[0];
}
...