133

在许多其他语言中,例如。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))

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

4

6 回答 6

170

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:

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);
于 2013-08-30T12:08:25.640 回答
73

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

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

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

重要的是,另一种方法可以在大约相同的行数中做同样的事情。

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

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

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

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

这是代码:

    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 版本存在相当大的差异,但总体而言,它似乎比 range 版本快 20 倍。(不过,我很愿意相信我做错了什么。)

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

于 2014-06-18T23:49:55.127 回答
38

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

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

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);
于 2013-08-30T22:33:48.717 回答
13

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

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

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

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

得到和等价于 Haskell 的

take 8 (repeat 1)

你可以写

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));
于 2015-10-15T07:24:46.447 回答
0

另一种选择是使用该Stream.generate()方法。例如,下面的代码片段将创建一个包含 5 个实例的列表MyClass

List<MyClass> timezones = Stream
    .generate(MyClass::createInstance)
    .limit(5)
    .collect(Collectors.toList());

来自java文档:

generate(Supplier s) 返回一个无限的顺序无序流,其中每个元素都是由提供的供应商生成的。

于 2022-02-27T03:26:33.550 回答
-1

这是我实现时间功能的解决方案。我是一名大三学生,所以我承认这可能并不理想,如果出于某种原因这不是一个好主意,我会很高兴。

public static <T extends Object, R extends Void> R times(int count, Function<T, R> f, T t) {
    while (count > 0) {
        f.apply(t);
        count--;
    }
    return null;
}

这是一些示例用法:

Function<String, Void> greet = greeting -> {
    System.out.println(greeting);
    return null;
};

times(3, greet, "Hello World!");
于 2020-08-01T16:08:16.460 回答