如何使用foreach循环遍历Java 8流?

30

假设我们尝试应用一个可能会抛出已检查异常的 lambda 到 Java 8 Stream 中:

Stream<String> stream = Stream.of("1", "2", "3");
Writer writer = new FileWriter("example.txt");

stream.forEach(s -> writer.append(s)); // Unhandled exception: java.io.IOException

这段代码无法编译。

一种解决方法是将已检查异常嵌套在RuntimeException中,但这会使以后的异常处理变得复杂且难看:

stream.forEach(s -> {
    try {
        writer.append(s);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
});

替代方法可能是将有限制的函数式forEach转换为更友好于可检查异常的旧式for循环循环

但是,简单的方法会失败:

for (String s : stream) { // for-each not applicable to expression type 'java.util.stream.Stream<java.lang.String>'
    writer.append(s);
}

for (String s : stream.iterator()) { // foreach not applicable to type 'java.util.Iterator<java.lang.String>'
    writer.append(s);
}

更新

之前在为什么Stream<T>没有实现Iterable<T>?上发布了一个技巧旁边的回答,它并没有真正回答这个问题本身。我认为这还不足以将此问题归类为那个问题的重复,因为它们询问的是不同的内容。


可能是重复的问题:Java 8 Lambda函数如何抛出异常? - blgt
@blgt,不,那个链接并没有给出答案。 - Vadzim
它确实适用于所述问题。阅读您的答案后,它可能更接近于为什么Stream<T>不实现Iterable<T>?,但您可能需要更明确地指定这些细节并突出差异。 - blgt
@blgt,在我看来,“howto”问题不能被评为“why doesn't”问题的完全重复,因为它们需要不同的答案。无论如何,提到的问题在搜索此问题主题时不会出现,因为其中没有明确提到foreach循环。 - Vadzim
@blgt,我已经按照你的要求更新了问题。顺便说一下,对于大多数开发人员(包括我自己),涉及检查异常的流迭代比抽象的StreamIterable转换实际问题更实用。 - Vadzim
类似问题:如何将Stream转换为Iterable? - Basil Bourque
4个回答

50

按照定义, foreach循环需要传入一个可迭代对象

可以通过匿名类来实现:

    for (String s : new Iterable<String>() {
        @Override
        public Iterator<String> iterator() {
            return stream.iterator();
        }
    }) {
        writer.append(s);
    }

这可以通过使用lambda来简化,因为Iterable是一个函数式接口
    for (String s : (Iterable<String>) () -> stream.iterator()) {
        writer.append(s);
    }

这可以转换为方法引用

    for (String s : (Iterable<String>) stream::iterator) {
        writer.append(s);
    }

可以通过中间变量或方法参数避免显式转换:

    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        writer.append(s);
    }

在Maven中央仓库还有StreamEx库,它提供了可迭代流和其他开箱即用的功能。


以下是有关在Lambda和Stream中处理Checked Exception的一些最流行的问题和方法:

Java 8 Lambda函数会抛出异常吗?

Java 8:Lambda-Streams,通过带有异常的方法进行过滤

如何在Java 8 streams中抛出已检查的异常?

Java 8:Lambda表达式中强制执行检查异常处理。为什么是强制性的而不是可选的?

jOOλ Unchecked

Lombok @SneakyThrows

Kotlin ;)


小细节:增强型for循环(foreach)也适用于不实现Iterable接口的数组。 - Brett Okken
@Brett,当然,这被省略了,因为与此案件细节无关。 - Vadzim
1
我最初在这里找到了这个技巧:(http://codereview.stackexchange.com/questions/70469/streamiterable-create-an-iterable-from-a-java-8-stream)。更详细的解释可以在这里找到:(https://dev59.com/ZmIj5IYBdhLWcg3wklyU)。希望这个问题能够成为人们搜索的热门问题,不像这些链接。 - Vadzim
3
有一种更通用的方法可以进行“hack”,它适用于任意高阶函数,而不仅仅是 forEach - Marko Topolnik

2
你也可以这样做:
Path inputPath = Paths.get("...");
Stream<Path> stream = Files.list(inputPath);

for (Iterator<Path> iterator = stream.iterator(); iterator.hasNext(); )
{
    Path path = iterator.next();
    // ...
}

2

流(Stream)既不实现 Iterable,也不是数组,因此不能在增强的 for 循环中使用。它不实现 Iterable 的原因是因为它是一个单次使用的数据结构。每次调用 Iterable.iterator 时,应返回一个新的迭代器,该迭代器涵盖所有元素。Stream 上的 iterator 方法只反映 Stream 的当前状态。它实际上是 Stream 的另一种视图。

您可以创建一个类,通过包装流来实现 Iterable,以便在增强的 for 循环中使用。但这是一个值得怀疑的想法,因为您无法真正地实现 Iterable(如上所述)。


2
我为Stream API编写了一个扩展,允许抛出已检查的异常。
Stream<String> stream = Stream.of("1", "2", "3");
Writer writer = new FileWriter("example.txt");

ThrowingStream.of(stream, IOException.class).forEach(writer::append);

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