Java 8 是否提供了重复值或函数的好方法?

2022-08-31 08:10:48

在许多其他语言中,例如。Haskell,很容易多次重复一个值或函数,例如。获取值 1 的 8 个副本的列表:

take 8 (repeat 1)

但是我还没有在Java 8中找到它。Java 8的JDK中有这样的功能吗?

或者等同于范围的东西,如

[1..8]

这似乎是Java中详细语句的明显替代品,例如

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}

拥有类似的东西

Range.from(1, 8).forEach(i -> System.out.println(i))

虽然这个特殊的例子实际上看起来并不简洁...但希望它更具可读性。


答案 1

对于此特定示例,您可以执行以下操作:

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);

如果需要与 1 不同的步骤,则可以使用映射函数,例如,对于步骤 2:

IntStream.rangeClosed(1, 8)
         .map(i -> 2 * i - 1)
         .forEach(System.out::println);

或者构建自定义迭代并限制迭代的大小:

IntStream.iterate(1, i -> i + 2)
         .limit(8)
         .forEach(System.out::println);

答案 2

这是我前几天遇到的另一种技术:

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));

该调用将创建一个包含您提供的任何值的副本。在本例中,它是盒装值 1。当然,它实际上并没有创建一个包含元素的列表;它创建一个仅包含值和长度的“虚拟化”列表,并且对范围内的任何调用都只返回该值。自从集合框架在JDK 1.2中引入以来,该方法就已经存在了。当然,Java SE 8 中添加了根据其结果创建流的功能。Collections.nCopiesListnIntegerngetnCopies

大不了,另一种在大约相同数量的行中做同样的事情的方法。

但是,这种技术比 and 方法更快,令人惊讶的是,它也比方法更快。IntStream.generateIntStream.iterateIntStream.range

因为和结果也许不太令人惊讶。流框架(实际上是这些流的拆分器)建立在这样的假设之上,即 lambda 每次都可能生成不同的值,并且它们将生成无限数量的结果。这使得并行拆分特别困难。对于这种情况,该方法也存在问题,因为每个调用都需要前一个调用的结果。因此,使用和的流在生成重复常量方面效果不佳。iterategenerateiterategenerateiterate

相对较差的性能令人吃惊。这也是虚拟化的,因此元素实际上并不都存在于内存中,并且大小是预先知道的。这应该是一个快速且易于并行化的分路器。但令人惊讶的是,它的表现并不好。也许原因是必须为范围的每个元素计算一个值,然后对其调用一个函数。但是这个函数只是忽略它的输入并返回一个常量,所以我很惊讶它没有内联和杀死。rangerange

该技术必须执行装箱/取消装箱才能处理值,因为 没有 的原始特化。由于每次的值都是相同的,因此它基本上被装箱一次,并且该框由所有副本共享。我怀疑拳击/拆箱是高度优化的,甚至是内切的,它可以很好地内联。Collections.nCopiesListn

代码如下:

    public static final int LIMIT = 500_000_000;
    public static final long VALUE = 3L;

    public long range() {
        return
            LongStream.range(0, LIMIT)
                .parallel()
                .map(i -> VALUE)
                .map(i -> i % 73 % 13)
                .sum();
}

    public long ncopies() {
        return
            Collections.nCopies(LIMIT, VALUE)
                .parallelStream()
                .mapToLong(i -> i)
                .map(i -> i % 73 % 13)
                .sum();
}

以下是JMH的结果:(2.8GHz Core2Duo)

Benchmark                    Mode   Samples         Mean   Mean error    Units
c.s.q.SO18532488.ncopies    thrpt         5        7.547        2.904    ops/s
c.s.q.SO18532488.range      thrpt         5        0.317        0.064    ops/s

ncopies版本有相当多的差异,但总的来说,它似乎比范围版本快20倍。(不过,我非常愿意相信我做错了什么。

我对这项技术的效果感到惊讶。在内部,它并没有做太多特别的事情,虚拟化列表的流只是使用!我原以为有必要创建一个专门的分离器来让它快速运行,但它似乎已经相当不错了。nCopiesIntStream.range


推荐