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

时间:2013-08-30 12:03:22

标签: java java-8

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

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

5 个答案:

答案 0 :(得分:131)

对于这个具体的例子,你可以这样做:

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

答案 1 :(得分:52)

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

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

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

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

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

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

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

我对nCopies技术的运作情况感到惊讶。在内部,它并不是非常特别,虚拟化列表的流只是使用IntStream.range来实现!我原本以为有必要创建一个专门的分裂器来让它快速发展,但它似乎已经相当不错了。

答案 2 :(得分:29)

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

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

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

答案 3 :(得分:9)

重复功能在某处定义为

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"));

答案 4 :(得分:0)

这是我实现times功能的解决方案。我是一名大三学生,所以我承认这可能并不理想,我很高兴听到这是否出于任何原因都不是个好主意。

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!");