使用流比较 Java 中的 HashMap 列表的正确方法是什么

What is the right way to compare the List of HashMap in Java using streams

我想比较 Java 中的两个 HashMap 列表。我想了解一种使用 lambda 表达式而不是嵌套 for 循环来比较它们的方法。

两个 HashMap 列表:

  1. productDetailsFromInputData;

示例数据:

    [{ProductName=Prod1, Quantity=1.0, Perpetual=true}, 
    {ProductName=Prod2, Quantity=1.0, Perpetual=false}, 
    {ProductName=Prod3, Quantity=1.0, Perpetual=false}]
  1. productDetailsFromApplication;

示例数据:

    [{Perpetual=true, ProductName=Prod1, Quantity=1.0}, 
    {Perpetual=true,  ProductName=Prod2, Quantity=1.0}, 
    {Perpetual=false, ProductName=Prod3, Quantity=2.0}, 
    {Perpetual=false, ProductName=Prod4, Quantity=1.0}, 
    {Perpetual=false, ProductName=Prod5, Quantity=1.0}, 
    {Perpetual=false, ProductName=Prod6, Quantity=1.0}] 

逻辑: 我想遍历 productDetailsFromInputData 列表并获取每个哈希映射的 ProductName 值,检查该值是否存在于 productDetailsFromApplication 列表中存在的任何哈希映射中。如果不是,则将结果标记为失败。

如果 ProductName 作为值存在于 productDetailsFromApplication 列表的任一散列映射中,则从该散列映射中获取 Perpetual 和 Quantity 值,并将其与 productDetailsFromInputData 列表中的散列映射进行比较。

到目前为止,我已经尝试过以下方法:

    for(HashMap<String,Object> inputProductHashMap : productDetailsFromInputData) {
           String productName = inputProductHashMap.get("ProductName").toString();
           String inputQuantity = inputProductHashMap.get("Quantity").toString();
           String inputPerpetual = inputProductHashMap.get("Perpetual").toString();
           LOGGER.info("Validating Product details for Product "+productName);
           if(productDetailsFromApplication.stream().anyMatch(t->t.containsValue(productName))){
               productDetailsFromApplication.stream()
                               .filter(p->p.containsValue(productName))
                               .findAny().ifPresent(p->
                               {
                                   String AppQuantity = p.get("Quantity").toString();
                                   String AppPerpetual = p.get("Perpetual").toString();
                                   LOGGER.info("Found the Product details in SFDC matching with input data");
                                   if(!inputQuantity.equalsIgnoreCase(AppQuantity)){
                                       LOGGER.error("APP Quantity does not matches with Input Quantity for Product "+productName
                                               +" App Quantity "+AppQuantity+" Input Quantity "+inputQuantity);
                                       Assert.fail("Product Validation Failed for "+productName);
                                   }
                                   if(!inputPerpetual.equalsIgnoreCase(AppPerpetual)){
                                       LOGGER.error("App Perpetual value does not matches with Input Perpetual value for Product "
                                               +productName +" App Perpetual "+AppPerpetual+" Input perpetual "+inputPerpetual);
                                       Assert.fail("Product Validation Failed "+productName);
                                   }
                               });

           }
           else {
               LOGGER.error("Did not find any matching Product Name with the given details in App");
               return false;
           }
        }

我想知道是否还有办法删除第一个 for 循环。由于我对使用 lambda 和流还很陌生,我该如何优化它。

此外,如果代码到达我当前添加断言以标记失败的 LOGGER.error 部分,我还想知道一种 return 布尔值的方法。

如果现有 Stack-overflow 问题涵盖了此问题的解决方案,请随时指导我。

根据您的陈述,我了解到您想要 return false 而不是 运行 Assert.fail。 只使用 Streams 的方法是:

return !productDetailsFromInputData.stream().map(i ->
        productDetailsFromApplication.stream().filter((a) -> a.get("ProductName").toString().equalsIgnoreCase(i.get("ProductName").toString())).findAny()
                .map(a ->
                        !a.get("Perpetual").toString().equalsIgnoreCase(i.get("Perpetual").toString()) || !a.get("Quantity").toString().equalsIgnoreCase(i.get("Quantity").toString()))
                .orElse(true)
).anyMatch(b -> b);

请注意,我将失败映射到 true,这是因为 anyMatch 会查找任何真实值。然后我们用 return.

中的 ! 否定它

如果你想保持 LOGGER 的冗长,你可以这样做:

return !productDetailsFromInputData.stream().map(i ->
        productDetailsFromApplication.stream().filter((a) -> a.get("ProductName").toString().equalsIgnoreCase(i.get("ProductName").toString())).findAny()
                .map(a -> {
                    if(!a.get("Quantity").toString().equalsIgnoreCase(i.get("Quantity").toString())) {
                        LOGGER.error("APP Quantity does not matches with Input Quantity for Product "+i.get("ProductName").toString()
                                +" App Quantity "+a.get("Quantity").toString()+" Input Quantity "+i.get("Quantity").toString());
                        return true;
                    }
                    if(!a.get("Perpetual").toString().equalsIgnoreCase(i.get("Perpetual").toString())) {
                        LOGGER.error("App Perpetual value does not matches with Input Perpetual value for Product "
                                +i.get("ProductName").toString() +" App Perpetual "+a.get("Perpetual").toString()+" Input perpetual "+i.get("Perpetual").toString());
                        return true;
                    }
                    return false;
                })
                .orElseGet(() -> {
                    LOGGER.error("Did not find any matching Product Name with the given details in App");
                    return true;
                })
).anyMatch(b -> b);

但是,我建议不要只使用 Streams 并先将 productDetailsFromApplication 转换为 Map,以便更快地找到匹配项:

Map<String, Map<String, Object>> mapFromApplication = productDetailsFromApplication.stream().collect(Collectors.toMap(e -> e.get("ProductName").toString(), Function.identity()));
return !productDetailsFromInputData.stream().map(i ->
        Optional.ofNullable(mapFromApplication.get(i.get("ProductName").toString()))
                .map(a -> {
                    if(!a.get("Quantity").toString().equalsIgnoreCase(i.get("Quantity").toString())) {
                        LOGGER.error("APP Quantity does not matches with Input Quantity for Product "+i.get("ProductName").toString()
                                +" App Quantity "+a.get("Quantity").toString()+" Input Quantity "+i.get("Quantity").toString());
                        return true;
                    }
                    if(!a.get("Perpetual").toString().equalsIgnoreCase(i.get("Perpetual").toString())) {
                        LOGGER.error("App Perpetual value does not matches with Input Perpetual value for Product "
                                +i.get("ProductName").toString() +" App Perpetual "+a.get("Perpetual").toString()+" Input perpetual "+i.get("Perpetual").toString());
                        return true;
                    }
                    return false;
                })
                .orElseGet(() -> {
                    LOGGER.error("Did not find any matching Product Name with the given details in App");
                    return true;
                })
).anyMatch(b -> b);

这将在第一次失败时停止,而不计算所有失败。如果您可以从记录所有失败 and/or 中获益,则可以使用 .filter(b -> b).count() 而不是 .anyMatch(b -> b)

long count = productDetailsFromInputData.stream().map(i ->
...
.filter(b -> b).count();
if(count>0) {
    LOGGER.error(count+" failures.");
    return false;
} else {
    return true;
}

编辑:您实际上可以将 anyMatch 语句进一步向上移动以替换第一个 map,但是将它放在底部可以让您轻松地将其更改为其他用途,例如 count 如果需要的话。