如何在Java中使用summingDouble(),summingLong() 和summingInt() 对集合中的元素求和

654 阅读7分钟

简介

一个流表示一个元素的序列,并支持不同类型的操作,以达到预期的结果。流的源头通常是一个集合或一个数组,数据从那里流出来。

流在几个方面与集合不同;最明显的是,流不是一个存储元素的数据结构。它们在本质上是功能性的,值得注意的是,对一个流的操作会产生一个结果,通常会返回另一个流,但不会修改其来源。

为了 "巩固 "这些变化,你将一个流的元素收集回一个Collection

许多简单的数学运算和概念在编程中找到了用武之地,而且大多数情况下,这些运算和概念在编程中就像它们在数学中一样简单明了。对元素进行求和也不例外,它的用法经常被忽视,因为有很多方法可以利用这个简单的任务。

在本指南中,我们将看看如何在Java中对一个集合中的所有元素进行求和,使用summingDouble(),summingLong()summingInt()

**注意:**值得注意的是,如果元素是可求和的,你可以对它们本身求和;如果元素是不可求和的,你也可以它们还原成数字表示,然后对还原部分求和。

收集器和Stream.collect()

收集器代表了Collector 接口的实现,它实现了各种有用的缩减操作,比如将元素累积到集合中,根据特定的参数对元素进行总结,等等。

所有预定义的实现都可以在Collectors 类中找到。

你也可以非常容易地实现你自己的收集器,并使用它来代替预定义的收集器--你可以用内置的收集器走得很远,因为它们涵盖了你可能想要使用它们的绝大多数情况。

为了能够在我们的代码中使用这个类,我们需要导入它。

import static java.util.stream.Collectors.*;

Stream.collect() 在流的元素上执行一个可变的还原操作。

可变还原操作在处理流中的元素时,将输入元素收集到一个可变容器中,比如Collection

*summing_()*采集器的定义

Collectors 类本身,我们发现有大量独特的方法来满足用户的不同需求。其中一组是由求和方法组成的--summingInt()summingDouble()summingLong()

尽管这些方法都是针对其名称中列出的独特的数据类型工作的,但它们的工作方式都很相似,只是略有不同。

public static <T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)

public static <T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)

public static <T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)

**注意:**方法签名中的通用T 代表我们正在处理的输入元素的类型。

前面我们说过,这些方法只能用于数字输入。预定义的ToIntFunction,ToDoubleFunctionToLongFunction fromjava.util.function 使我们能够准确地进行这种转换--从对象类型到其原始的int, double, long 类型。

在我们开始深入研究每个方法之前,我们将定义一个类,在我们的例子中,我们将在一些字段旁边使用。

这个类将被称为Product

public class Product {
    private String name;
    private Integer quantity;
    private Double price;
    private Long productNumber;

    // Constructor, getters and setters
}

该类有四个不同类型的字段。

  • String name - 我们最终不会使用这个字段,因为这是一个非数字值,但为了有意义的例子,我们需要给我们的产品命名。
  • Integer quantity - 我们库存中产品的数量。
  • Double price - 产品的价格。
  • Long productNumber - 每个产品的六位数代码。

除了这四个字段,我们还有一个简单的构造函数,以及除了产品本身的名称之外的所有东西的获取器。让我们也在主程序中实例化我们的List 产品。

List<Product> products = Arrays.asList(
        new Product("Milk", 37, 3.60, 12345600L),
        new Product("Carton of Eggs", 50, 1.20, 12378300L),
        new Product("Olive oil", 28, 37.0, 13412300L),
        new Product("Peanut butter", 33, 4.19, 15121200L),
        new Product("Bag of rice", 26, 1.70, 21401265L)
);

**注意:**除了这个我们必须简化为一个字段以便求和的类之外,我们还将看看只由IntegerDoubleLong 等元素组成的Lists 的例子。

Collectors.summingInt()

summingInt() 方法返回一个Collector ,产生应用于输入元素的整数值函数的和。换句话说--它将集合中的整数相加并返回结果。在没有输入元素的情况下,返回值是0

List 让我们从一个基本的例子开始,Integers

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println("Sum: " + sum);

我们应用.stream() 方法来创建一个Integer 实例流,之后我们使用之前讨论过的.collect() 方法来收集元素,使用summingInt() 。该方法本身也接受ToIntFunction ,它可以用来将实例减少到一个可以求和的整数。

由于我们已经使用了Integer,我们可以简单地传入一个方法引用,表示它们的intValue ,因为不需要进一步的还原。

Sum: 15

更多的时候,你会处理自定义对象的列表,并希望对它们中的一些字段进行求和。例如,我们可以将productList 中每个产品的数量相加,表示我们拥有的总库存。

在这种情况下,我们可以使用一个方法引用,比如Product::getQuantity 作为我们的ToIntFunction ,将这些对象还原成一个个的整数,然后对这些整数求和。

Integer sumOfQuantities = products.stream().collect(Collectors.summingInt(Product::getQuantity));
System.out.println("Total number of products: " + sumOfQuantities);

这样的结果是

Total number of products: 174

如果你想阅读更多关于缩减和缩减操作的内容,这通常是求和和平均操作的核心,还有其他任务--请阅读我们的Java 8 Streams - Guide to reduce()

Collectors.summingDouble()

summingInt() 返回整数值之和的方式差不多,summingDouble() 返回双倍值之和的结果。

然而,这个方法在一个方面与summingInt() 不同。由于累积的四舍五入误差,返回的总和可能因记录数值的顺序而不同。按递增顺序排序的值往往能产生更准确的结果。

**注意:**如果任何一个值是NaN ,或者任何一点的总和是NaN ,结果也将是NaN

让我们从一个双数的列表开始。

List<Double> numbers = Arrays.asList(3.0, 5.5, 11.3, 40.3, 21.1);
Double sum = numbers.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println(sum);

在对我们的输出进行四舍五入后,它就会打印出来。

81.2

同样,我们通常使用自定义对象,而不是包装器/原始类型。我们产品的价格是用双数表示的,所以我们可以对价格进行求和。如果我们要购买库存中每个产品的一个实例,价格会是多少?

让我们用summingDouble() 来获取价格的报价。

Double sumOfPrices = products.stream().collect(Collectors.summingDouble(Product::getPrice));
System.out.println("The total sum of prices: " + sumOfPrices);

这就给了我们

The total sum of prices: 47.69

如果我们想有点创意,计算所有产品加起来的总价格,我们也可以用这个方法来做。然而,它确实需要事先进行计算,这就大大方便了。

List<Double> productPrices = new ArrayList<>();
for(Product product : products){
    productPrices.add(product.getPrice() * product.getQuantity());
}

Double sumOfPrices = productPrices.stream().collect(Collectors.summingDouble(Double::doubleValue));
System.out.println("Sum of all product prices : " + sumOfPrices);

运行这段代码,我们可以得到以下结果。

Sum of all product prices : 1411.67

Collectors.summingLong()

求和方法组的第三个也是最后一个方法是summingLong() 。这个方法和前两个方法一样,返回一个Collector ,产生一个应用于输入元素的长值函数的总和。如果没有元素,结果是0

List<Long> numbers = Arrays.asList(23L, 11L, 13L, 49L, 7L);
Long sum = numbers.stream().collect(Collectors.summingLong(Long::longValue));
System.out.println(sum);

这个输出

103

最后,我们的productNumber 字段的类型是Long 。结果发现,这些数字是经过精心挑选的,一旦分割开来并从十进制转换为ASCII,就可以对一个秘密信息进行编码。我们将对这些长条进行求和,并编写一个自定义的辅助函数来解码信息。

Long productNumbers = products.stream().collect(Collectors.summingLong(Product::getProductNumber));
System.out.println(productNumbers);
System.out.println(decodeMessage(productNumbers));

decodeMessage() 方法接收一个Long ,将其拆开,并将每两个字符视为一个Character 的十进制表示,然后将这些字符相加并返回信息。

public static String decodeMessage(Long encodedMessage) {
    String message = String.valueOf(encodedMessage);
    String[] characters = message.split("");
    StringBuilder result = new StringBuilder();

    for (int i = 1; i < characters.length; i+=2) {
        result.append(Character.toString(Integer.parseInt(characters[i-1]+characters[i])));
    }

    return result.toString();
}

很好,让我们看看结果

74658665
JAVA

无论如何,这不是一个令人兴奋的秘密信息,但还是一个信息。

总结

在本指南中,我们看了一下求和收集器,并探索了summingInt(),summingDouble()summingLong() 方法。

我们探讨了它们在原始包装器以及自定义对象上的用法,这些对象通常被简化为一个字段用于求和操作。