Estoy tratando de usar Java 8 Stream
s para encontrar elementos en a LinkedList
. Sin embargo, quiero garantizar que haya una y solo una coincidencia con los criterios de filtro.
Toma este código:
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;
}
}
Este código encuentra un User
basado en su ID. Pero no hay garantías de cuántos User
coinciden con el filtro.
Cambiar la línea del filtro a:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Lanzará un NoSuchElementException
(¡bueno!)
Sin embargo, me gustaría que arroje un error si hay varias coincidencias. ¿Hay alguna forma de hacer esto?
java
lambda
java-8
java-stream
ryvantage
fuente
fuente
count()
es una operación de terminal, así que no puedes hacer eso. La secuencia no se puede usar después.Stream::size
?Stream
mucho más de lo que lo hice antes ...LinkedHashSet
(suponiendo que desea preservar el orden de inserción) oHashSet
todo el tiempo. Si su colección solo se utiliza para encontrar una única identificación de usuario, ¿por qué está recopilando todos los demás elementos? Si existe la posibilidad de que siempre necesite encontrar alguna identificación de usuario que también deba ser única, ¿por qué usar una lista y no un conjunto? Estás programando al revés. Use la colección adecuada para el trabajo yRespuestas:
Crea una costumbre
Collector
Usamos
Collectors.collectingAndThen
para construir nuestro deseadoCollector
porList
con elCollectors.toList()
coleccionista.IllegalStateException
iflist.size != 1
.Usado como:
Luego puede personalizarlo
Collector
todo lo que desee, por ejemplo, dar la excepción como argumento en el constructor, ajustarlo para permitir dos valores y más.Una solución alternativa, posiblemente menos elegante:
Puede usar una 'solución' que implique
peek()
y unaAtomicInteger
, pero realmente no debería estar usando eso.Lo que podrías hacer es simplemente recogerlo en un archivo
List
como este:fuente
Iterables.getOnlyElement
acortaría estas soluciones y proporcionaría mejores mensajes de error. Solo como un consejo para otros lectores que ya usan Google Guava.singletonCollector()
definición obsoleta por la versión que permanece en la publicación, y renombrándola atoSingleton()
. Mi experiencia en la transmisión de Java está un poco oxidada, pero el cambio de nombre me parece útil. Revisar este cambio me llevó 2 minutos, como máximo. Si no tiene tiempo para revisar las ediciones, ¿puedo sugerirle que pida a otra persona que haga esto en el futuro, tal vez en la sala de chat de Java ?En aras de la exhaustividad, aquí está el 'one-liner' correspondiente a la excelente respuesta de @prunge:
Esto obtiene el único elemento coincidente de la secuencia, arrojando
NoSuchElementException
en caso de que la secuencia esté vacía, oIllegalStateException
en caso de que la secuencia contenga más de un elemento coincidente.Una variación de este enfoque evita lanzar una excepción antes de tiempo y, en cambio, representa el resultado como que
Optional
contiene el único elemento o nada (vacío) si hay cero o múltiples elementos:fuente
get()
aorElseThrow()
Las otras respuestas que implican escribir una costumbre
Collector
son probablemente más eficientes (como Louis Wasserman's , +1), pero si quieres ser breve, te sugiero lo siguiente:Luego verifique el tamaño de la lista de resultados.
fuente
limit(2)
esta solución? ¿Qué diferencia habría si la lista resultante fuera 2 o 100? Si es mayor que 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
. Entonces, ¿no debería ser en.limit(1)
lugar de.limit(2)
?result.size()
para asegurarse de que sea igual a 1. Si es 2, entonces hay más de una coincidencia, por lo que es un error. Si el código lo hicieralimit(1)
, más de una coincidencia daría como resultado un solo elemento, que no se puede distinguir de que haya exactamente una coincidencia. Esto pasaría por alto un caso de error que preocupaba al OP.Guava proporciona lo
MoreCollectors.onlyElement()
que hace lo correcto aquí. Pero si tiene que hacerlo usted mismo, puede rodar el suyoCollector
para esto:... o usando su propio
Holder
tipo en lugar deAtomicReference
. Puedes reutilizar esoCollector
tanto como quieras.fuente
Collector
era el camino a seguir.List
es más costoso que una sola referencia mutable.MoreCollectors.onlyElement()
debería ser la primera (y quizás la única :))Utilice la guayaba
MoreCollectors.onlyElement()
( JavaDoc ).Hace lo que quiere y arroja un
IllegalArgumentException
si la secuencia consta de dos o más elementos, y unNoSuchElementException
si la secuencia está vacía.Uso:
fuente
MoreCollectors
es parte del aún inédito (a partir de 2016-12) inédita versión 21.La operación de "escotilla de escape" que le permite hacer cosas raras que no son compatibles con las transmisiones es solicitar un
Iterator
:La guayaba tiene un método conveniente para tomar
Iterator
y obtener el único elemento, lanzando si hay cero o múltiples elementos, lo que podría reemplazar las líneas n-1 inferiores aquí.fuente
Actualizar
Buena sugerencia en el comentario de @Holger:
Respuesta original
La excepción se produce
Optional#get
, pero si tiene más de un elemento, eso no ayudará. Puede recopilar los usuarios en una colección que solo acepta un elemento, por ejemplo:que arroja un
java.lang.IllegalStateException: Queue full
, pero eso se siente demasiado hacky.O podría usar una reducción combinada con un opcional:
La reducción esencialmente devuelve:
El resultado se envuelve en un opcional.
Pero la solución más simple probablemente sería simplemente recolectar en una colección, verificar que su tamaño sea 1 y obtener el único elemento.
fuente
null
) para evitar el usoget()
. Lamentablemente, tureduce
no funciona como crees que funciona, considera unoStream
que tenganull
elementos, tal vez pienses que lo cubriste, pero puedo[User#1, null, User#2, null, User#3]
hacerlo, ahora creo que no arrojará una excepción, a menos que me equivoque aquí.null
a la función de reducción, eliminación del argumento valor de identidad haría que todo el trato connull
la función obsoleta:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
tiene el trabajo y aún mejor, ya que devuelve unaOptional
, elidiendo la necesidad de llamarOptional.ofNullable
a la resultado.Una alternativa es usar la reducción: (este ejemplo usa cadenas pero podría aplicarse fácilmente a cualquier tipo de objeto incluido
User
)Entonces para el caso con
User
usted tendría:fuente
Usando reducir
Esta es la forma más simple y flexible que encontré (basada en la respuesta @prunge)
De esta manera obtienes:
Optional.empty()
si no está presentefuente
Creo que de esta manera es más simple:
fuente
Usando un
Collector
:Uso:
Devolvemos un
Optional
, ya que generalmente no podemos asumirCollection
que contiene exactamente un elemento. Si ya sabe que este es el caso, llame al:Esto pone la carga de manejar el error en la persona que llama, como debería ser.
fuente
La guayaba tiene un
Collector
llamado para estoMoreCollectors.onlyElement()
.fuente
Podemos usar RxJava ( biblioteca de extensión reactiva muy poderosa )
El operador único genera una excepción si no se encuentra ningún usuario o más de uno.
fuente
Como
Collectors.toMap(keyMapper, valueMapper)
utiliza una fusión de lanzamiento para manejar múltiples entradas con la misma clave, es fácil:Obtendrá una
IllegalStateException
para claves duplicadas. Pero al final no estoy seguro de si el código no sería aún más legible usando unif
.fuente
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, tienes un comportamiento más genérico.Estoy usando esos dos coleccionistas:
fuente
onlyOne()
tiraIllegalStateException
para> 1 elementos, y NoSuchElementException` (inOptional::get
) para 0 elementos.Supplier
de(Runtime)Exception
.Si no le importa usar una biblioteca de terceros,
SequenceM
desde cyclops-streams (yLazyFutureStream
desde simple-react ), ambos tienen operadores opcionales únicos y únicos.singleOptional()
lanza una excepción si hay0
o más de1
elementos en elStream
, de lo contrario, devuelve el valor único.singleOptional()
devuelveOptional.empty()
si no hay valores o más de un valor en elStream
.Divulgación: soy el autor de ambas bibliotecas.
fuente
Seguí el enfoque directo y simplemente implementé la cosa:
con la prueba JUnit:
Esta implementación no es segura para subprocesos.
fuente
fuente
¿Has probado esto?
Fuente: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
fuente
count()
no es bueno usarlo porque es una operación terminal.