如何在Java 8中创建漂亮的迭代器

4

Adam Bien的博客启发,我想将Java 7中常见的迭代替换为Java 8中更好的迭代方式。旧代码如下:

void repeatUsingJava7(int times) {
    for (int i = 0; i < times; i++) {
        doStuff();
        doMoreStuff();
        doEvenMoreStuff();
    }
}

...这并不是很好。所以我用Adam Bein的例子将其替换为:

void repeatUsingJava8(int times) {
    IntStream.range(0, times).forEach(
        i -> {
            doStuff();
            doMoreStuff();
            doEvenMoreStuff();
        }
    );
}

这是一种正确的方法,但并没有使代码更易读,并且引入了不必要的变量i和额外的花括号。因此,我想知道是否有其他方法可以编写这个代码,使其更加美观和易于阅读,主要使用Java 8。


可能是Java 8 Iterable.forEach() vs foreach loop的重复问题。 - Bartosz Bilicki
我看不出它们有什么相似之处:我的问题是关于重复操作多次,而你提到的那一个是关于在列表上进行迭代。 - uzilan
1
表达代码的第二种方式是不引入“一个不必要的变量i以及额外的一对花括号”。两个代码片段都有一个变量i,并且都有*相同数量的花括号。嗯,它们是等价的... - Holger
4个回答

5
仅为完整起见,这里提供一种不需要计数器变量的解决方案:
void repeatUsingJava8(int times) {
    Collections.<Runnable>nCopies(times, ()->{
        doStuff();
        doMoreStuff();
        doEvenMoreStuff();
    }).forEach(Runnable::run);
}

如果只有一个方法需要被多次调用,那么将使代码更易读,例如:

void repeatUsingJava8(int times) {
    Collections.<Runnable>nCopies(times, this::doStuff).forEach(Runnable::run);
}

如果必须使用“流”,则上面的代码等同于:
void repeatUsingJava8(int times) {
    Stream.<Runnable>generate(()->this::doStuff).limit(times).forEach(Runnable::run);
}

然而,这些替代方案并不比好老的for循环更好。如果考虑到parallel执行,这是Stream比普通for循环的真正优势,仍然有基于常见、已批准的API的替代方案:
ExecutorService es=Executors.newCachedThreadPool();
es.invokeAll(Collections.nCopies(times, Executors.callable(()->{
    doStuff();
    doMoreStuff();
    doEvenMoreStuff();
})));

4

我认为在这种情况下使用流没有任何优势。流适用于处理包含同一类型多个元素的集合,数组和其他数据结构。

在这里您不需要处理任何数据,您只需要多次重复相同的操作。因此,没有理由将传统的for循环替换为流。


我认为这使得代码更具表现力,这也是Java 8中流和Lambda的全部意义所在。但我同意,我写第二个示例的方式并没有使代码更简单,这就是为什么我在问是否有其他方法可以编写它! - uzilan
5
常见的危险是每个人都想用新光亮的工具做所有事情,即使旧的生锈工具仍然可以使用。当我看到IDE建议将集合上的普通for循环转换为forEach时,大多数情况下这是错误的做法。在新工具可以提供支持的地方使用它们。但使用for循环也没有问题。 - Brian Goetz

1
正如其他人所指出的那样,用流替换简单的for循环并不一定更好,对于这个特定的例子而言。然而,如果你尝试解决的问题更加普遍,优点开始显现。例如,假设你需要重复一些作为参数传递的代码片段,而不是重复一些特定的代码n次,请考虑:
void repeat(int count, Runnable action) {
    IntStream.range(0, count).forEach(i -> action.run());
}

现在您可以编写:


repeat(3, () -> System.out.println("Hello!"));

或者,可能是这样。
repeat(4, this::doStuff);

也许你不只想执行一个动作n次,而是想执行多个动作n次。你可以这样做:
void repeat(int count, Runnable... actions) {
    IntStream.range(0, count).forEach(i -> Arrays.asList(actions).forEach(Runnable::run));
}

然后你就可以写:

repeat(5, this::doStuff, this::doMoreStuff, this::doEvenMoreStuff);

repeat 实现比传统的 Java 7 方式更加简洁(或许是简洁到了极致):

void repeatOldWay(int count, Runnable...actions) {
    for (int i = 0; i < count; i++) {
        for (Runnable r : actions) {
            r.run();
        }
    }
}

真正的优势在于调用处,您可以仅传递计数和一个或多个lambda表达式或方法引用,而不是复制嵌套的for循环逻辑。

0

你也可以使用 from predicates:

IntStream.range(0, 10).sorted().filter(i -> i > 5).forEach(System.out::println);

sorted和filer是额外的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接