web-dev-qa-db-fra.com

La bonne façon de renvoyer le seul élément d'un ensemble

J'ai le genre de situation suivant:

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

En supposant que je ne puisse pas changer le type de retour de getSetFromSomewhere(), y a-t-il un moyen meilleur ou plus correct de renvoyer le seul élément de l'ensemble que

  • Itération sur l'ensemble et retour immédiat
  • Créer une liste à partir de l'ensemble et appeler .get(0)
44
Janne

Vous pouvez utiliser un Iterator pour à la fois obtenir le seul élément et vérifier que la collection ne contient qu'un seul élément (évitant ainsi l'appel size() et la création de liste inutile):

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;

Vous concluriez généralement cela dans sa propre méthode:

public static <E> E getOnlyElement(Iterable<E> iterable) {
    Iterator<E> iterator = iterable.iterator();

    // The code I mentioned above...
}

Notez que cette implémentation fait déjà partie des Google Bibliothèques de goyaves (que je recommande fortement , même si vous ne l'utilisez pas pour ce code particulier). Plus précisément, la méthode appartient à la classe Iterables :

Element element = Iterables.getOnlyElement(set);

Si vous êtes curieux de savoir comment il est implémenté, vous pouvez consulter le code source de la classe Iterators (les méthodes de Iterables appellent souvent des méthodes de 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());
  }
52
Adam Paynter

La meilleure solution générale (où vous ne connaissez pas la classe d'ensemble réelle) est:

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

Si la classe set est connue pour être un SortedSet (par exemple un TreeSet ou ConcurrentSkipListSet), alors une meilleure solution est:

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

Dans les deux cas, une exception sera levée si l'ensemble est vide; vérifiez les javadocs. L'exception peut être évitée en utilisant Collection.isEmpty().


La première solution est O(1) dans le temps et l'espace pour un HashSet ou LinkedHashSet, mais généralement pire pour les autres types d'ensemble.

Le second est O(logN) dans le temps, et n'utilise aucun espace pour TreeSet ou ConcurrentSkipListSet.

L'approche consistant à créer une liste à partir du contenu défini puis à appeler List.get(0) donne une mauvaise solution car la première étape est une opération O(N), à la fois dans le temps et dans l'espace.


Je n'ai pas remarqué que N est en fait 1. Mais même ainsi, la création d'un itérateur est probablement moins coûteuse que la création d'une liste temporaire.

17
Stephen C

Vous pouvez saisir l'itérateur:

Element firstEl = set.iterator().next();
9
Mark Elliot

En Java 8, nous pouvons faire comme ci-dessous:

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

Mais n'oubliez pas de vérifier la taille de l'ensemble avant que Optional.get() jette NoSuchElementException

0
Liquidpie