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

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

take 8 (repeat 1)

但是我在 Java8中还没有发现这个函数,在 Java8的 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))

虽然这个例子看起来不是很简洁,但是希望它更具可读性。

89332 次浏览

对于这个特定的例子,您可以这样做:

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

为了完整,也是因为我情不自禁:)

生成一个有限的常量序列与您在哈斯克尔中看到的非常接近,只是 Java 级别的冗长。

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

这是我前几天遇到的另一个技巧:

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

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

有什么了不起的,用同样数量的行来做同样的事情。

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

对于 iterategenerate来说,结果可能并不太令人惊讶。流框架(实际上,这些流的 Spliterators)是基于这样的假设构建的: lambdas 每次都可能生成不同的值,并且它们将生成无限多的结果。这使得平行分裂变得尤其困难。对于这种情况,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.8 GHz 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

在 ncopy 版本中有相当数量的差异,但总体来说它似乎比音域版本快20倍。(不过,我很愿意相信自己做错了什么。)

我对 nCopies技术的工作效果感到惊讶。在内部,它没有做很多特别的事情,虚拟化列表的流只是使用 IntStream.range来实现!我曾经期望有必要创建一个专门的分裂器来使其快速运行,但它似乎已经非常好了。

一旦重复函数被定义为

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

这是我实现 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!");

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

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

来自 java 文档:

产生(供应商) 返回无限顺序无序 流中的每个元素是由提供的供应商生成的。