Tengo una entidad con 10 campos:
Class Details{ String item; String name; String type; String origin; String color; String quality; String country; String quantity; Boolean availability; String price; }Tengo un punto final tranquilo que sirve una Lista. Quiero que el usuario pueda proporcionar filtros de búsqueda para cada campo. Actualmente tengo QueryParam para cada campo. Luego filtro usando java8 stream:
List<Detail> details; details.stream().filter(detail-> detail.getItem()==item).filter(detail-> detail.getName()==name).....collect(Collectors.toList());Si tengo otras 50 clases con múltiples campos que quiero filtrar, ¿hay alguna forma de generalizar esto?
Puede componer dichos predicados con .and() y .or() , lo que le permite definir un único predicado agregado que aplica todas las comprobaciones que desea, en lugar de intentar encadenar n llamadas .filter() . Esto permite predicados arbitrariamente complejos que se pueden construir en tiempo de ejecución.
// Note that you shouldn't normally use == on objects Predicate<Detail> itemPredicate = d-> item.equals(d.getItem()); Predicate<Detail> namePredicate = d-> name.equals(d.getName()); details.stream() .filter(itemPredicate.and(namePredicate)) .collect(Collectors.toList());Si quieres evitar la reflexión ¿qué tal algo así?
static enum DetailQueryParams { ITEM("item", d -> d.item), NAME("name", d -> d.name), TYPE("type", d -> d.type), ORIGIN("origin", d -> d.origin), COLOR("color", d -> d.color), QUALITY("quality", d -> d.quality), COUNTRY("country", d -> d.country), QUANTITY("quantity", d -> d.quantity), AVAILABILITY("availability", d -> d.availability), PRICE("price", d -> d.price); private String name; private Function<Detail, Object> valueExtractor; private DetailQueryParams(String name, Function<Detail, Object> valueExtractor) { this.name = name; this.valueExtractor = valueExtractor; } public static Predicate<Detail> mustMatchDetailValues( Function<String, Optional<String>> queryParamGetter) { return Arrays.asList(values()).stream() .map(p -> queryParamGetter.apply(p.name) .map(q -> (Predicate<Detail>) d -> String.valueOf(p.valueExtractor.apply(d)).equals(q)) .orElse(d -> true)) .reduce(Predicate::and) .orElse(d -> true); } } Y luego, suponiendo que puede acceder a los parámetros de consulta, por ejemplo request.getQueryParam(String name) que devuelve un valor de String o null , use el código llamando a lo siguiente:
details.stream() .filter(DetailQueryParams.mustMatchDetailValues( name -> Optional.ofNullable(request.getQueryParam(name)))) .collect(Collectors.toList());Lo que el método básicamente hace es:
- for each possible query param - get its value from the request - if value is present build predicate which - gets field value from detail object and convert to string - check that both strings (queried and extracted) matches - if value is not present return predicate that always returns true - combine resulting predicates using and - use always true as fallback (which here never actually happens)Por supuesto, esto también podría extenderse para generar predicados dependiendo del tipo de valor real en lugar de comparar cadenas para que, por ejemplo, los rangos puedan solicitarse y manejarse a través de "priceMin" y/o "priceMax".
Para detalles de Lambda -> condición. Entonces podemos especificar cuántos se requieren.
details.stream().filter(detail-> detail.getItem()==item && detail.getName()==name && ...)