Tengo objetos entrantes con una estructura normalizada de-plano que me crea una instancia de un conjunto de resultados JDBC. Los objetos de entrada reflejan el conjunto de resultados, hay un montón de datos repetidos, así que quiero convertir los datos en una lista de objetos padres con colecciones hijas anidados, es decir, un gráfico de objetos, o una lista normalizada.
la clase del objeto entrante se ve así:
class IncomingFlatItem {
String clientCode;
String clientName;
String emailAddress;
boolean emailHtml;
String reportCode;
String reportLanguage;
}
Lo que los datos de entrada contiene varios objetos para cada cliente, lo que me gustaría a agregarse en un objeto de cliente, que contiene una lista de objetos de dirección de correo electrónico para el cliente, y una lista de objetos de informe.
Por lo tanto el objeto de cliente se vería así:
class Client {
String clientCode;
String clientName;
Set<EmailAddress> emailAddresses;
Set<Report> reports;
}
Por extraño que no puedo encontrar una respuesta existente para esto. Estoy buscando en la anidación corrientes o flujos de encadenamiento pero me gustaría encontrar el enfoque más elegante y definitivamente quiero evitar un bucle para.
Gracias a todos los que responden que han mencionado Collectors.groupingBy()
. Esto fue clave para la creación de un flujo en el que podía utilizar reduce()
. Yo había creído erróneamente que debería ser capaz de utilizar reduce
por sí solo para resolver el problema, sin groupingBy
.
Gracias también a la sugerencia de crear una API fluida. He añadido IncomingFlatItem.getEmailAddress()
y IncomingFlatItem.getReport()
de fluidez agarrar los objetos de dominio de IncomingFlatItem
- y también un método para convertir todo el elemento plano a un objeto de dominio adecuado con su correo electrónico y el informe ya anidado:
public Client getClient() {
Client client = new Client();
client.setClientCode(clientCode);
client.setClientName(clientName);
client.setEmailAddresses(new ArrayList());
client.getEmailAddresses().add(this.getEmailAddress());
client.setReports(new ArrayList<>());
client.getReports().add(this.getReport());
return client;
}
También basado en el ID de empresa creada .equals()
y .hashCode()
métodos sobre Client
, EmailAddress
y Report
según lo recomendado por @SamuelPhilip
Por último para los objetos de dominio, creé .addReport(Report r)
y .addEmail(EmailAddress e)
en mi Client
clase, lo que añadiría el objeto secundario que Client
si no está ya presente. Dejé el Set
tipo de colección para List
debido a que el modelo estándar es de dominio List
y Sets
habría significado una gran cantidad de conversiones a Lists
.
Así que con eso, el código de secuencia y lambdas ven sucinta.
Hay 3 pasos:
- asignar
IncomingFlatItems
aClients
- grupo de la
Clients
en un mapa por el cliente (depender en gran medidaClient.equals()
) - reducir cada grupo a una
Client
Así que este es el algoritmo funcional:
List<Client> unflatten(List<IncomingFlatItem> flatItems) {
return flatItems.parallelStream()
.map(IncomingFlatItem::getClient)
.collect(Collectors.groupingByConcurrent(client -> client))
.entrySet().parallelStream()
.map(kvp -> kvp.getValue()
.stream()
.reduce(new Client(),
(client1, client2) -> {
client1.getReports()
.forEach(client2::addReport);
client1.getEmailAddresses()
.forEach(client2::addEmail);
return client2;
}))
.collect(Collectors.toList());
}
Me tomó mucho tiempo debido a salirse por la tangente antes de que realmente entendí reduce
- he encontrado una solución que pasaba mis pruebas durante el uso .stream()
, pero fracasó totalmente con .parallelStream()
por lo tanto, su uso aquí. Tuve que usar CopyOnWriteArrayList
además de lo contrario sería caer al azar conConcurrentModificationExceptions