J'essaie d'utiliser Java 8 Stream
pour trouver des éléments dans a LinkedList
. Je veux cependant garantir qu'il y a une et une seule correspondance avec les critères de filtrage.
Prenez ce code:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
@Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
Ce code trouve un User
basé sur leur ID. Mais rien ne garantit le nombre de User
s correspondant au filtre.
Changer la ligne de filtre en:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Jettera un NoSuchElementException
(bon!)
Je voudrais cependant qu'il génère une erreur s'il existe plusieurs correspondances. Y a-t-il un moyen de faire cela?
java
lambda
java-8
java-stream
ryvantage
la source
la source
count()
est une opération de terminal, vous ne pouvez donc pas le faire. Le flux ne peut pas être utilisé après.Stream::size
?Stream
bien plus qu'avant ...LinkedHashSet
(en supposant que vous souhaitiez conserver l'ordre d'insertion) ou unHashSet
tout le long. Si votre collection n'est utilisée que pour trouver un seul identifiant utilisateur, pourquoi collectez-vous tous les autres éléments? S'il existe un potentiel dont vous aurez toujours besoin pour trouver un identifiant utilisateur qui doit également être unique, alors pourquoi utiliser une liste et non un ensemble? Vous programmez à l'envers. Utilisez la bonne collection pour le travail et épargnez-vous ce mal de têteRéponses:
Créer un personnalisé
Collector
Nous utilisons
Collectors.collectingAndThen
pour construire notre désirCollector
parList
avec leCollectors.toList()
collectionneur.IllegalStateException
iflist.size != 1
.Utilisé comme:
Vous pouvez ensuite personnaliser cela
Collector
autant que vous le souhaitez, par exemple donner l'exception comme argument dans le constructeur, l'ajuster pour autoriser deux valeurs, et plus encore.Une solution alternative - sans doute moins élégante -:
Vous pouvez utiliser une solution de contournement qui implique
peek()
et unAtomicInteger
, mais vous ne devriez vraiment pas utiliser cela.Ce que vous pourriez faire, c'est simplement le collecter dans un
List
, comme ceci:la source
Iterables.getOnlyElement
raccourciraient ces solutions et fourniraient de meilleurs messages d'erreur. Juste un conseil pour les autres lecteurs qui utilisent déjà Google Guava.singletonCollector()
définition obsolète par la version qui reste dans la publication et en la renommanttoSingleton()
. Mon expertise en flux Java est un peu rouillée, mais le changement de nom me semble utile. La révision de ce changement m'a pris 2 minutes, en tête. Si vous n'avez pas le temps de revoir les modifications, puis-je vous suggérer de demander à quelqu'un d'autre de le faire à l'avenir, peut-être dans la salle de chat Java ?Par souci d'exhaustivité, voici le «one-liner» correspondant à l'excellente réponse de @ prunge:
Cela obtient le seul élément correspondant du flux, jetant
NoSuchElementException
dans le cas où le flux est vide, ouIllegalStateException
dans le cas où le flux contient plus d'un élément correspondant.Une variante de cette approche évite de lever une exception plus tôt et représente à la place le résultat comme
Optional
contenant soit le seul élément, soit rien (vide) s'il y a zéro ou plusieurs éléments:la source
get()
enorElseThrow()
Les autres réponses qui impliquent l'écriture d'une coutume
Collector
sont probablement plus efficaces (comme celle de Louis Wasserman , +1), mais si vous voulez être bref, je suggère ce qui suit:Vérifiez ensuite la taille de la liste des résultats.
la source
limit(2)
cette solution? Quelle différence cela ferait-il si la liste résultante était 2 ou 100? Si elle est supérieure à 1.Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })
maxSize: the number of elements the stream should be limited to
. Donc, il ne devrait pas être au.limit(1)
lieu de.limit(2)
?result.size()
pour s'assurer qu'il est égal à 1. Si c'est 2, alors il y a plus d'une correspondance, c'est donc une erreur. Si le code le faisait à la placelimit(1)
, plus d'une correspondance entraînerait un seul élément, qui ne peut pas être distingué de l'existence d'une seule correspondance. Cela raterait un cas d'erreur qui inquiétait le PO.La goyave fournit
MoreCollectors.onlyElement()
ce qui fait la bonne chose ici. Mais si vous devez le faire vous-même, vous pouvez lancer le vôtreCollector
pour cela:... ou en utilisant votre propre
Holder
type au lieu deAtomicReference
. Vous pouvez le réutiliserCollector
autant que vous le souhaitez.la source
Collector
était la voie à suivre.List
est plus coûteuse qu'une seule référence mutable.MoreCollectors.onlyElement()
devrait en fait être la première (et peut-être la seule :))Utilisez Guava
MoreCollectors.onlyElement()
( JavaDoc ).Il fait ce que vous voulez et lance un
IllegalArgumentException
si le flux est composé de deux éléments ou plus, et unNoSuchElementException
si le flux est vide.Usage:
la source
MoreCollectors
fait partie de la version 21 non encore publiée (à partir de 2016-12) nonL'opération "hachure d'échappement" qui vous permet de faire des choses étranges qui ne sont pas autrement prises en charge par les flux consiste à demander un
Iterator
:La goyave a une méthode pratique pour prendre un
Iterator
et obtenir le seul élément, en jetant s'il y a zéro ou plusieurs éléments, ce qui pourrait remplacer les n-1 lignes inférieures ici.la source
Mettre à jour
Belle suggestion dans le commentaire de @Holger:
Réponse originale
L'exception est levée par
Optional#get
, mais si vous avez plus d'un élément, cela n'aidera pas. Vous pouvez collecter les utilisateurs dans une collection qui n'accepte qu'un seul élément, par exemple:qui jette un
java.lang.IllegalStateException: Queue full
, mais qui semble trop hacky.Ou vous pouvez utiliser une réduction combinée avec une option:
La réduction renvoie essentiellement:
Le résultat est ensuite enveloppé dans une option.
Mais la solution la plus simple serait probablement de simplement collecter dans une collection, de vérifier que sa taille est de 1 et d'obtenir le seul élément.
la source
null
) pour empêcher l'utilisationget()
. Malheureusement, votrereduce
ne fonctionne pas comme vous le pensez, pensez à un élémentStream
qui contient desnull
éléments, peut-être pensez-vous que vous l'avez couvert, mais je peux l'être[User#1, null, User#2, null, User#3]
, maintenant il ne lèvera pas d'exception, je pense, à moins que je ne me trompe ici.null
à la fonction de réduction, la suppression de l'argument de valeur d'identité rendrait l'ensemble du traitement denull
la fonction obsolète:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
fait le travail et encore mieux, il renvoie déjà unOptional
, éliminant la nécessité d'appelerOptional.ofNullable
le résultat.Une alternative est d'utiliser la réduction: (cet exemple utilise des chaînes mais pourrait facilement s'appliquer à n'importe quel type d'objet, y compris
User
)Donc, dans le cas où
User
vous auriez:la source
Utilisation de réduire
C'est la manière la plus simple et flexible que j'ai trouvée (basée sur la réponse @prunge)
De cette façon, vous obtenez:
Optional.empty()
s'il n'est pas présentla source
Je pense que cette façon est plus simple:
la source
En utilisant un
Collector
:Usage:
Nous retournons un
Optional
, car nous ne pouvons généralement pas supposer que leCollection
contient exactement un élément. Si vous savez déjà que c'est le cas, appelez:Cela met le fardeau de gérer l'erreur sur l'appelant - comme il se doit.
la source
La goyave a un
Collector
pour cela appeléMoreCollectors.onlyElement()
.la source
Nous pouvons utiliser RxJava ( bibliothèque d' extensions réactives très puissante )
L' opérateur unique lève une exception si aucun utilisateur ou plus d'un utilisateur n'est trouvé.
la source
Comme
Collectors.toMap(keyMapper, valueMapper)
utilise une fusion de lancement pour gérer plusieurs entrées avec la même clé, il est facile:Vous obtiendrez un
IllegalStateException
pour les clés en double. Mais à la fin, je ne sais pas si le code ne serait pas encore plus lisible en utilisant unif
.la source
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, vous avez un comportement plus générique.J'utilise ces deux collecteurs:
la source
onlyOne()
lanceIllegalStateException
pour> 1 éléments et NoSuchElementException` (inOptional::get
) pour 0 éléments.Supplier
de(Runtime)Exception
.Si cela ne vous dérange pas d'utiliser une bibliothèque tierce, à
SequenceM
partir de flux cyclops (etLazyFutureStream
de simple-react ), les deux ont des opérateurs single et singleOptional.singleOptional()
lève une exception s'il y a0
ou plus d'1
éléments dans leStream
, sinon il retourne la valeur unique.singleOptional()
renvoieOptional.empty()
s'il n'y a pas de valeurs ou plus d'une valeur dans leStream
.Divulgation - Je suis l'auteur des deux bibliothèques.
la source
Je suis allé avec l'approche directe et je viens de mettre en œuvre la chose:
avec le test JUnit:
Cette implémentation n'est pas threadsafe.
la source
la source
As-tu essayé
Source: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
la source
count()
n'est pas bon à utiliser car c'est une opération de terminal.