Java 8是否提供重复值或函数的好方法?
在许多其他语言中,例如。 Haskell,很容易重复一个值或函数多次,例如。 得到8份价值清单1:
take 8 (repeat 1)
但是我还没有在Java 8中find这个。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))
虽然这个特殊的例子看起来并不简单,但希望它更具可读性。
对于这个具体的例子,你可以这样做:
IntStream.rangeClosed(1, 8) .forEach(System.out::println);
如果您需要与1不同的步骤,则可以使用映射function,例如,步骤为2:
IntStream.rangeClosed(1, 8) .map(i -> 2 * i - 1) .forEach(System.out::println);
或者构build一个自定义迭代并限制迭代的大小:
IntStream.iterate(1, i -> i + 2) .limit(8) .forEach(System.out::println);
这是我另外一天运行的另一种技术:
Collections.nCopies(8, 1) .stream() .forEach(i -> System.out.println(i));
Collections.nCopies
调用创build一个List
其中包含您提供的任何值的n
副本。 在这种情况下,它是盒装的Integer
值1.当然,它实际上并没有创build一个包含n
元素的列表。 它会创build一个只包含值和长度的“虚拟化”列表,任何在范围内的调用get
返回值。 自从Collections Framework在JDK 1.2中引入之后, nCopies
方法就已经出现了。 当然,在Java SE 8中添加了从结果创buildstream的function。
重要的是,在同样数量的行中做同样的事情的另一种方法。
但是,这种技术比IntStream.generate
和IntStream.iterate
方法更快,令人惊讶的是,它比IntStream.range
方法更快。
对于iterate
和generate
结果可能不是太奇怪。 stream框架(实际上,这些stream的Spliterator)build立在lambda将会每次可能产生不同值的假设上,并且它们将产生无限数量的结果。 这使得平行分裂特别困难。 iterate
方法对于这种情况也是有问题的,因为每个调用都需要前一个调用的结果。 所以使用generate
和iterate
的stream不能很好地生成重复的常量。
range
相对较差的performance令人惊讶。 这也是虚拟化的,所以元素实际上并不全都存在于内存中,并且尺寸是事先知道的。 这应该是一个快速和容易并行的分割器。 但是,令人惊讶的是,这并没有做得很好。 也许原因是range
必须为range
的每个元素计算一个值,然后调用一个函数。 但是这个函数只是忽略了它的input并返回一个常量,所以我很惊讶这不是内联和死亡。
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 csqSO18532488.ncopies thrpt 5 7.547 2.904 ops/s csqSO18532488.range thrpt 5 0.317 0.064 ops/s
在ncopies版本中有相当大的差异,但是总体来说,它似乎比范围版本快20倍。 (不过,我很乐意相信我做错了什么)。
我很惊讶nCopies
技术的工作。 在内部,它并没有什么特别之处,虚拟化列表的stream只是使用IntStream.range
来实现的! 我原本以为有必要创build一个专门的分裂者来让这个快速发展,但它已经很不错了。
为了完整性,也因为我忍不住:)
生成一个有限的常量序列非常接近你在Haskell中看到的,只有Java级别的冗长。
IntStream.generate(() -> 1) .limit(8) .forEach(System.out::println);
一旦重复函数定义为某处
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"));