Подтвердить что ты не робот

Правильный способ возврата единственного элемента из набора

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

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

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

  • Итерация по множеству и немедленное возвращение
  • Создание списка из набора и вызов .get(0)
4b9b3361

Ответ 1

Вы можете использовать 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...
}

Обратите внимание, что эта реализация уже является частью библиотек Guava (которую я очень рекомендую, даже если вы не используете ее для этого конкретного код). Более конкретно, метод относится к классу 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());
  }

Ответ 2

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

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

Если установленный класс известен как TreeSet, то лучшим решением является:

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

Эти 2 решения O(1) во времени и пространстве для a HashSet или LinkedHashSet (были применимы) и O(logN) во времени /O(1) в пространстве для a TreeSet.

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


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

Ответ 3

Вы можете захватить итератор:

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

Ответ 4

if(set.size()==1){
   set.toArray(new Element[0])[0];
}