使用流api对列表数据进行多次操作

时间:2016-01-30 20:25:17

标签: java functional-programming java-8 java-stream

我需要计算篮子价格。我将项目列表作为输入,并首先使用地图计算每个项目的价格。之后我必须计算篮子总数并返回ShoppingBasketOutput。我在下面附上了我的代码,但我不喜欢将ShoppingBasketOutput作为参数传递给calculateBasketPrice。

您是否有更好的建议来实施此要求?

public ShoppingBasketOutput getBasketTotalPrice(List<ShoppingItemDTO> itemsDTO) {
    ShoppingBasketOutput shoppingBasketOutput = new ShoppingBasketOutput();

    itemsDTO.stream()
            .map(this::calculateItemPrice)
            .forEach((item) -> calculateBasketPrice(shoppingBasketOutput, item));
    return shoppingBasketOutput;
}

private ShoppingBasketOutput calculateBasketPrice(ShoppingBasketOutput shoppingBasketOutput, ShoppingItemDTO item) {
    shoppingBasketOutput.setSubTotal(shoppingBasketOutput.getSubTotal() + item.getItemSubTotal());
    shoppingBasketOutput.setTotalDiscount(shoppingBasketOutput.getTotalDiscount() + item.getTotalItemDiscount());
    return shoppingBasketOutput;
}

private ShoppingItemDTO calculateItemPrice(ShoppingItemDTO shoppingItemDTO) {
    Optional<ItemEnum> itemEnum = ItemEnum.getItem(shoppingItemDTO.getName());
    if (itemEnum.isPresent()) {
        ItemEnum item = itemEnum.get();
        shoppingItemDTO.setTotalItemDiscount(getItemTotalDiscount(item, shoppingItemDTO.getQuantity()));
        shoppingItemDTO.setItemSubTotal(item.getPrice() * shoppingItemDTO.getQuantity());
    } else {
        throw new ProductNotFoundException();
    }
    return shoppingItemDTO;
}

4 个答案:

答案 0 :(得分:2)

你的方式错了。

首先要记住map(mapper)要求映射函数为:

  

应用于每个元素的非干扰无状态函数

在您的情况下,非干扰且无状态,因为您正在修改Stream元素,即您的ShoppingItemDTO。所以这很糟糕。

我们如何纠正这个?好吧,我们需要考虑一下我们想要实现的目标。您的代码会为每个ShoppingItemDTO计算两个值,然后将它们汇总在一起以生成ShoppingBasketOutput

所以,你想要的是reduce你的itemsDTOShoppingBasketOutput。要使用的标识是新的空ShoppingBasketOutput。每次我们遇到一个项目时,我们都会在篮子输出中添加小计和折扣。第三个参数通过将它们的值相加来将两个篮子输出组合在一起。

public ShoppingBasketOutput getBasketTotalPrice(List<ShoppingItemDTO> itemsDTO) {
    return itemsDTO.stream().reduce(
        new ShoppingBasketOutput(),
        (output, item) -> {
            ItemEnum itemEnum = ItemEnum.getItem(item.getName()).orElseThrow(ProductNotFoundException::new);
            output.setSubTotal(output.getSubTotal() + itemEnum.getPrice() * item.getQuantity());
            output.setTotalDiscount(output.getTotalDiscount() + getItemTotalDiscount(itemEnum, item.getQuantity()));
            return output;
        },
        (output1, output2) -> {
            output1.setSubTotal(output1.getSubTotal() + output2.getSubTotal());
            output1.setTotalDiscount(output1.getTotalDiscount() + output2.getTotalDiscount());
            return output1;
        }
    );
}

请注意,通过引入两个方法addToSubTotal(amount)addToTotalDiscount(amount),您可以使此代码更加清晰。使用这些方法,您需要获取并设置新值。

答案 1 :(得分:2)

这与Tunaki的答案相同,我将其标记为已接受的答案,并通过将逻辑移动到单独的方法而不是将所有内容转换为相同的方法来重构一点。

public ShoppingBasketOutput getBasketTotalPrice(List<ShoppingItemDTO> itemsDTO) {
    return itemsDTO.stream()
            .reduce(
                    new ShoppingBasketOutput(),
                    this::calculateItemPrice,
                    this::calculateBasketPrice
            );
}

private ShoppingBasketOutput calculateBasketPrice(ShoppingBasketOutput firstOutput, ShoppingBasketOutput secondOutput) {
    firstOutput.setSubTotal(firstOutput.getSubTotal() + secondOutput.getSubTotal());
    firstOutput.setTotalDiscount(firstOutput.getTotalDiscount() + secondOutput.getTotalDiscount());
    return firstOutput;
}

private ShoppingBasketOutput calculateItemPrice(ShoppingBasketOutput output, ShoppingItemDTO item) {
    ItemEnum itemEnum = ItemEnum.getItem(item.getName()).orElseThrow(ProductNotFoundException::new);
    output.setSubTotal(output.getSubTotal() + itemEnum.getPrice() * item.getQuantity());
    output.setTotalDiscount(output.getTotalDiscount() + getItemTotalDiscount(itemEnum, item.getQuantity()));
    return output;
}

答案 2 :(得分:0)

为什么calculateItemPrice会返回它的参数。它可能是void

试试这个(未经测试)

public ShoppingBasketOutput getBasketTotalPrice(List<ShoppingItemDTO> itemsDTO) {
    ShoppingBasketOutput shoppingBasketOutput = new ShoppingBasketOutput();

    itemsDTO.forEach(this::calculateItemPrice);

    shoppingBasketOutput.setSubTotal(
      itemsDTO.stream().mapToDouble(ip -> ip.getSubTotal()).sum()
    );

    shoppingBasketOutput.setTotalDiscount(
      itemsDTO.stream().mapToDouble(ip -> ip.getTotalDiscount()).sum()
    );

    return shoppingBasketOutput;
}

答案 3 :(得分:0)

假设您有可用的构造函数,并且不想创建任何其他类:

public ShoppingBasketOutput getBasketTotalPrice(List<ShoppingItemDTO> itemsDTO) {
    ShoppingItemDTO res = itemsDTO.stream().map(this::calculateItemPrice).reduce(new ShoppingItemDTO(0, 0),
            (l, r) -> new ShoppingItemDTO(l.getItemSubTotal() + r.getItemSubTotal(),
                    l.getItemTotalDiscount() + r.getItemTotalDiscount()));

    return new ShoppingBasketOutput(res.getItemSubTotal(), res.getItemTotalDiscount());
}