Estoy tratando de usar Java 8 Streams 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 Userbasado en su ID. Pero no hay garantías de cuántos Usercoinciden 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?Streammucho más de lo que lo hice antes ...LinkedHashSet(suponiendo que desea preservar el orden de inserción) oHashSettodo 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
CollectorUsamos
Collectors.collectingAndThenpara construir nuestro deseadoCollectorporListcon elCollectors.toList()coleccionista.IllegalStateExceptioniflist.size != 1.Usado como:
Luego puede personalizarlo
Collectortodo 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
Listcomo este:fuente
Iterables.getOnlyElementacortarí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
NoSuchElementExceptionen caso de que la secuencia esté vacía, oIllegalStateExceptionen 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
Optionalcontiene 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
Collectorson 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 suyoCollectorpara esto:... o usando su propio
Holdertipo en lugar deAtomicReference. Puedes reutilizar esoCollectortanto como quieras.fuente
Collectorera el camino a seguir.Listes 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
IllegalArgumentExceptionsi la secuencia consta de dos o más elementos, y unNoSuchElementExceptionsi la secuencia está vacía.Uso:
fuente
MoreCollectorses 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
Iteratory 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, tureduceno funciona como crees que funciona, considera unoStreamque tenganullelementos, 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í.nulla la función de reducción, eliminación del argumento valor de identidad haría que todo el trato connullla 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.ofNullablea 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
Userusted 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 asumirCollectionque 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
Collectorllamado 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
IllegalStateExceptionpara 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()tiraIllegalStateExceptionpara> 1 elementos, y NoSuchElementException` (inOptional::get) para 0 elementos.Supplierde(Runtime)Exception.Si no le importa usar una biblioteca de terceros,
SequenceMdesde cyclops-streams (yLazyFutureStreamdesde simple-react ), ambos tienen operadores opcionales únicos y únicos.singleOptional()lanza una excepción si hay0o más de1elementos 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.