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
.get(0)
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());
}
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.
Vous pouvez saisir l'itérateur:
Element firstEl = set.iterator().next();
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