我有传入的对象,它们具有从JDBC结果集中实例化的平面非规范化结构。传入的对象反映了结果集,其中包含大量重复数据,因此我想将数据转换为带有嵌套子集合的父对象列表,即对象图或规范化列表。
传入对象的类如下:
class IncomingFlatItem {
String clientCode;
String clientName;
String emailAddress;
boolean emailHtml;
String reportCode;
String reportLanguage;
}
因此,传入的数据包含每个客户端的多个对象,我想将其聚合到一个客户端对象中,该对象包含该客户端的电子邮件地址对象列表和报告对象列表。
所以Client对象看起来像这样:
class Client {
String clientCode;
String clientName;
Set<EmailAddress> emailAddresses;
Set<Report> reports;
}
奇怪的是,我找不到现有的答案。我正在查看嵌套流或链接流,但是我想找到最优雅的方法,并且我绝对希望避免for循环。
答案 0 :(得分:1)
您可以做的一件事是使用构造函数参数和流利的API来发挥自己的优势。想到“嵌套”流和流API(带有动态数据)会很快变得复杂。
这只是使用流利的API简化了事情(您可以改用合适的构建器模式)
class Client {
String clientCode;
String clientName;
Set<EmailAddress> emailAddresses = new HashSet<>();
Set<Report> reports = new HashSet<>();
public Client(String clientCode, String clientName) {
super();
this.clientCode = clientCode;
this.clientName = clientName;
}
public Client emailAddresses(String address, boolean html) {
this.emailAddresses =
Collections.singleton(new EmailAddress(address, html));
return this;
}
public Client reports(String... reports) {
this.reports = Arrays.stream(reports)
.map(Report::new)
.collect(Collectors.toSet());
return this;
}
public Client merge(Client other) {
this.emailAddresses.addAll(other.emailAddresses);
this.reports.addAll(other.reports);
if (null == this.clientName)
this.clientName = other.clientName;
if (null == this.clientCode)
this.clientCode = other.clientCode;
return this;
}
}
class EmailAddress {
public EmailAddress(String e, boolean html) {
}
}
class Report {
public Report(String r) {
}
}
然后...
Collection<Client> clients = incomingFlatItemsCollection.stream()
.map(flatItem -> new Client(flatItem.clientCode, flatItem.clientName)
.emailAddresses(flatItem.emailAddress, flatItem.emailHtml)
.reports(flatItem.reportCode, flatItem.reportLanguage))
.collect(Collectors.groupingBy(Client::getClientCode,
Collectors.reducing(new Client(null, null), Client::merge)))
.values();
或者您也可以只使用将IncomingFlatItem
对象转换为Client
的映射函数。
答案 1 :(得分:1)
您可以使用映射功能将List<IncomingFlatItem>
转换为Set<Reports/EmailAddress>
的方式:
Function<List<IncomingFlatItem>, Set<EmailAddress>> inferEmailAddress =
incomingFlatItems -> incomingFlatItems.stream()
.map(obj -> new EmailAddress(obj.getEmailAddress(),
obj.isEmailHtml()))
.collect(Collectors.toSet());
Function<List<IncomingFlatItem>, Set<Report>> inferReports =
incomingFlatItems -> incomingFlatItems.stream()
.map(obj -> new Report(obj.getReportCode(),
obj.getReportLanguage()))
.collect(Collectors.toSet());
,并进一步使用groupingBy
并将条目映射为List<Client>
:
List<Client> transformIntoGroupedNormalisedContent(
List<IncomingFlatItem> incomingFlatItemList) {
return incomingFlatItemList.stream()
.collect(Collectors.groupingBy(inc ->
Arrays.asList(inc.getClientCode(), inc.getClientName())))
.entrySet()
.stream()
.map(e -> new Client(e.getKey().get(0),
e.getKey().get(1),
inferEmailAddress.apply(e.getValue()),
inferReports.apply(e.getValue())))
.collect(Collectors.toList());
}
答案 2 :(得分:1)
您可以使用此:
List<Client> clients = items.stream()
.collect(Collectors.groupingBy(i -> Arrays.asList(i.getClientCode(), i.getClientName())))
.entrySet().stream()
.map(e -> new Client(e.getKey().get(0), e.getKey().get(1),
e.getValue().stream().map(i -> new EmailAddress(i.getEmailAddress(), i.isEmailHtml())).collect(Collectors.toSet()),
e.getValue().stream().map(i -> new Report(i.getReportCode(), i.getReportLanguage())).collect(Collectors.toSet())))
.collect(Collectors.toList());
在开始时,您将项目按clientCode
和clientName
进行分组。然后,将结果映射到Client
对象。
确保为.equals()
和hashCode()
实现了EmailAddress
和Report
方法,以确保它们在集合中是不同的。
答案 3 :(得分:1)
感谢所有提到Collectors.groupingBy()
的回答者。这是设置可以使用reduce()
的流的关键。我曾经错误地认为我应该能够单独使用reduce
来解决问题,而无需使用groupingBy
。
也感谢创建一个流畅的API的建议。我添加了IncomingFlatItem.getEmailAddress()
和IncomingFlatItem.getReport()
来从IncomingFlatItem
流畅地获取域对象-以及一种将整个平面项目转换为带有其电子邮件和报告且已经嵌套的适当域对象的方法:< / p>
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;
}
我还按照@SamuelPhilip的建议,在.equals()
,.hashCode()
和Client
上创建了基于业务ID的EmailAddress
和Report
方法
最后,对于域对象,我在.addReport(Report r)
类上创建了.addEmail(EmailAddress e)
和Client
,如果尚不存在,则会将子对象添加到Client
中。我放弃了Set
的{{1}}集合类型,因为域模型标准是List
,而List
意味着要向Sets
进行大量转换。
因此,流代码和lambda看起来很简洁。
共有3个步骤:
Lists
映射到IncomingFlatItems
Clients
分组到地图中(严重依赖Clients
)Client.equals()
这是功能算法:
Client
由于切线太长,我花了很长时间才真正了解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());
}
-我找到了一种解决方案,该解决方案在使用reduce
时通过了我的测试,但是在.stream()
时完全失败了它在这里的用法。我也必须使用.parallelStream()
,否则它将被CopyOnWriteArrayList
答案 4 :(得分:0)
如果您不想遍历条目集(不想处理Map.Entry
),或者更喜欢没有groupingBy
的其他解决方案,则也可以将toMap
与合并功能以汇总您的值。这种方法之所以有效,是因为Client
可以容纳初始的单个项目以及所有EmailAddress
的累积集合(注意:为简洁起见,我使用了一个实用函数com.google.common.collectSets.union
,但是您可以使用例如HashSet)。
以下代码演示了如何执行此操作(以与EmailAddress相同的方式添加Reports,并添加所需的其他字段)。我保留了合并功能的内联功能,没有添加AllArgsConstructor,但是可以自由重构。
static Client mapFlatItemToClient(final IncomingFlatItem item) {
final Client client = new Client();
client.clientCode = item.clientCode;
client.emailAddresses = Collections.singleton(mapFlatItemToEmail(item));
return client;
}
static EmailAddress mapFlatItemToEmail(final IncomingFlatItem item) {
final EmailAddress address = new EmailAddress();
address.emailAddress = item.emailAddress;
return address;
}
public static void example() {
final List<IncomingFlatItem> items = new ArrayList<>();
// Aggregated Client Info by Client Code
final Map<String, Client> intermediateResult = items.stream()
.collect(
Collectors.<IncomingFlatItem, String, Client> toMap(
flat -> flat.clientCode,
flat -> mapFlatItemToClient(flat),
(lhs, rhs) -> {
final Client client = new Client();
client.clientCode = lhs.clientCode;
client.emailAddresses = Sets.union(lhs.emailAddresses, rhs.emailAddresses);
return client;
}));
final Collection<Client> aggregatedValues = intermediateResult.values();
}