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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18532488/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 08:49:55  来源:igfitidea点击:

Does Java 8 provide a good way to repeat a value or function?

javajava-8

提问by Graeme Moss

In many other languages, eg. Haskell, it is easy to repeat a value or function multiple times, eg. to get a list of 8 copies of the value 1:

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

take 8 (repeat 1)

but I haven't found this yet in Java 8. Is there such a function in Java 8's JDK?

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

Or alternatively something equivalent to a range like

或者相当于一个范围的东西

[1..8]

It would seem an obvious replacement for a verbose statement in Java like

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

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

to have something like

有类似的东西

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

though this particular example doesn't look much more concise actually... but hopefully it's more readable.

虽然这个特定的例子实际上看起来并没有更简洁……但希望它更具可读性。

采纳答案by assylias

For this specific example, you could do:

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

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

If you need a step different from 1, you can use a mapping function, for example, for a step of 2:

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

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

Or build a custom iteration and limit the size of the iteration:

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

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

回答by clstrfsck

For completeness, and also because I couldn't help myself :)

为了完整性,也因为我无法帮助自己:)

Generating a limited sequence of constants is fairly close to what you would see in Haskell, only with Java level verboseness.

生成有限的常量序列与您在 Haskell 中看到的非常接近,只是具有 Java 级别的冗长性。

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);

回答by Stuart Marks

Here's another technique I ran across the other day:

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

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

The Collections.nCopiescall creates a Listcontaining ncopies of whatever value you provide. In this case it's the boxed Integervalue 1. Of course it doesn't actually create a list with nelements; it creates a "virtualized" list that contains only the value and the length, and any call to getwithin range just returns the value. The nCopiesmethod has been around since the Collections Framework was introduced way back in JDK 1.2. Of course, the ability to create a stream from its result was added in Java SE 8.

Collections.nCopies调用会创建一个List包含n您提供的任何值的副本。在这种情况下,它是装箱Integer值 1。当然,它实际上并没有创建一个包含n元素的列表;它创建了一个仅包含值和长度的“虚拟化”列表,对get范围内的任何调用都只返回该值。nCopies自从 JDK 1.2 中引入集合框架以来,该方法就一直存在。当然,从结果创建流的能力是在 Java SE 8 中添加的。

Big deal, another way to do the same thing in about the same number of lines.

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

However, this technique is faster than the IntStream.generateand IntStream.iterateapproaches, and surprisingly, it's also faster than the IntStream.rangeapproach.

然而,这种技术比IntStream.generateIntStream.iterate方法快,而且令人惊讶的是,它也比IntStream.range方法快。

For iterateand generatethe result is perhaps not too surprising. The streams framework (really, the Spliterators for these streams) is built on the assumption that the lambdas will potentially generate different values each time, and that they will generate an unbounded number of results. This makes parallel splitting particularly difficult. The iteratemethod is also problematic for this case because each call requires the result of the previous one. So the streams using generateand iteratedon't do very well for generating repeated constants.

对于iterategenerate结果也许不算稀奇。流框架(实际上,这些流的 Spliterators)建立在这样的假设之上,即 lambda 每次都可能生成不同的值,并且它们将生成无限数量的结果。这使得并行拆分特别困难。该iterate方法在这种情况下也有问题,因为每次调用都需要前一次调用的结果。因此,使用generate和的流iterate在生成重复常量方面表现不佳。

The relatively poor performance of rangeis surprising. This too is virtualized, so the elements don't actually all exist in memory, and the size is known up front. This should make for a fast and easily parallelizable spliterator. But it surprisingly didn't do very well. Perhaps the reason is that rangehas to compute a value for each element of the range and then call a function on it. But this function just ignores its input and returns a constant, so I'm surprised this isn't inlined and killed.

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

The Collections.nCopiestechnique has to do boxing/unboxing in order to handle the values, since there are no primitive specializations of List. Since the value is the sameevery time, it's basically boxed once and that box is shared by all ncopies. I suspect boxing/unboxing is highly optimized, even intrinsified, and it can be inlined well.

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

Here's the code:

这是代码:

    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();
}

And here are the JMH results: (2.8GHz Core2Duo)

这是 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

There is a fair amount of variance in the ncopies version, but overall it seems comfortably 20x faster than the range version. (I'd be quite willing to believe that I've done something wrong, though.)

ncopies 版本存在相当多的差异,但总体而言,它似乎比 range 版本快 20 倍。(不过,我很愿意相信我做错了什么。)

I'm surprised at how well the nCopiestechnique works. Internally it doesn't do very much special, with the stream of the virtualized list simply being implemented using IntStream.range! I had expected that it would be necessary to create a specialized spliterator to get this to go fast, but it already seems to be pretty good.

我对这项nCopies技术的效果感到惊讶。在内部,它并没有什么特别之处,虚拟化列表的流只是使用IntStream.range! 我原以为有必要创建一个专门的拆分器来使其快速运行,但它似乎已经很不错了。

回答by Hartmut P.

Once a repeat function is somewhere defined as

一旦重复函数在某处定义为

public static BiConsumer<Integer, Runnable> repeat = (n, f) -> {
    for (int i = 1; i <= n; i++)
        f.run();
};

You can use it now and then this way, e.g.:

您可以不时地以这种方式使用它,例如:

repeat.accept(8, () -> System.out.println("Yes"));

To get and equivalent to Haskell's

获得并等效于 Haskell 的

take 8 (repeat 1)

You could write

你可以写

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));