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

Gra*_*oss 110 java java-8

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

take 8 (repeat 1)
Run Code Online (Sandbox Code Playgroud)

但我还没有在Java 8中找到它.在Java 8的JDK中是否有这样的功能?

或者相当于范围的东西

[1..8]
Run Code Online (Sandbox Code Playgroud)

它似乎是Java中冗长语句的明显替代品

for (int i = 1; i <= 8; i++) {
    System.out.println(i);
}
Run Code Online (Sandbox Code Playgroud)

有类似的东西

Range.from(1, 8).forEach(i -> System.out.println(i))
Run Code Online (Sandbox Code Playgroud)

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

ass*_*ias 138

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

IntStream.rangeClosed(1, 8)
         .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

如果需要与1不同的步骤,则可以使用映射函数,例如,步骤2:

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

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

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

  • @jwenting对于有FP经验的人来说,围绕高阶函数的代码是纯粹的胜利.对于没有这种经验的人来说,是时候升级你的技能了 - 或者冒着被遗忘的风险. (8认同)
  • 闭包将完全转换Java代码,更好.期待那一天...... (4认同)
  • @MarkoTopolnik您可能想要使用更新版本的javadoc(您​​指向构建78,最新版本是构建105:http://download.java.net/lambda/b105/docs/api/java/util/stream /package-summary.html) (2认同)

Stu*_*rks 58

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

Collections.nCopies(8, 1)
           .stream()
           .forEach(i -> System.out.println(i));
Run Code Online (Sandbox Code Playgroud)

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

重要的是,在相同数量的线上做同样的事情的另一种方式.

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

因为iterate,generate结果可能并不太令人惊讶.流框架(实际上,这些流的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();
}
Run Code Online (Sandbox Code Playgroud)

以下是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
Run Code Online (Sandbox Code Playgroud)

ncopies版本中存在相当大的差异,但整体而言它似乎比范围版本快20倍.(不过我会非常愿意相信我做错了.)

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

  • 当经验不足的开发人员得知`nCopies`实际上并没有_copy_任何东西而且"副本"都指向那个单个对象时,他们可能会感到困惑或陷入困境.如果该对象是_immutable_,则总是安全的,例如本例中的盒装基元.你在"盒装一次"声明中提到了这一点,但是在这里明确地引出警告可能会很好,因为这种行为并不特定于自动装箱. (5认同)
  • 的确很好 (2认同)
  • 为了完整起见,很高兴看到一个将这两种技术与普通的旧“for”循环进行比较的基准测试。虽然您的解决方案比“Stream”代码更快,但我的猜测是“for”循环将大大击败其中任何一个。 (2认同)

msa*_*ord 32

为了完整,还因为我无法自救:)

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

IntStream.generate(() -> 1)
         .limit(8)
         .forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

  • 是的,根据OP的第一个Haskell示例`take 8(repeat 1)`.assylias几乎涵盖了所有其他案例. (2认同)
  • `Stream <T>`还有一个通用的`generate`方法,用于获取其他类型的无限流,可以用同样的方式限制它. (2认同)

Har*_* P. 10

一旦重复函数被定义为

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

你现在可以使用它,然后这样,例如:

repeat.accept(8, () -> System.out.println("Yes"));
Run Code Online (Sandbox Code Playgroud)

获得和等同于Haskell的

take 8 (repeat 1)
Run Code Online (Sandbox Code Playgroud)

你可以写

StringBuilder s = new StringBuilder();
repeat.accept(8, () -> s.append("1"));
Run Code Online (Sandbox Code Playgroud)

  • 这个很棒。但是我通过将Runnable更改为Function &lt;Integer,?&gt;然后使用f.apply(i)对其进行了修改,以提供迭代次数。 (2认同)