如何在Stream<String> .forEach()中插入计数器?

31
FileWriter writer = new FileWriter(output_file);
    int i = 0;

    try (Stream<String> lines = Files.lines(Paths.get(input_file))) {
        lines.forEach(line -> {
            try {
                writer.write(i + " # " + line + System.lineSeparator());
            } catch (Exception e) {
                e.printStackTrace();
            }   
        }
                    );
        writer.close();
    }

我需要在每一行写上行号,所以我尝试在 .forEach() 中添加一个计数器,但是我无法让它工作。我不知道在代码中放置 i++; 的位置,到目前为止随意尝试并没有帮助。


1
在循环外声明一个整数,将其设置为0,并在每次迭代中增加它。 - Stultuske
@Stultuske 这个可行吗?你试过了吗?请注意这里没有循环,但有一个lambda函数。 - user253751
那是个问题,我有一个int i = 0;定义在lambda外面,但我无法让它在每个for.Each()循环中递增。Stultuske既没有读代码也没有看问题,只看了标题。 - Vega
1
你尝试过 writer.write(i++ + " # " + line + System.lineSeparator()) 吗? - Veselin Davidov
创建一个类Counter的最终实例,该类有1个变量,即int,设置为0。每次迭代时增加Counter实例的计数器。对象的final意味着您无法重新引用它,但这并不意味着您不能更改其值(除非您在Counter中将该变量声明为final)。 - Stultuske
显示剩余2条评论
3个回答

59

您可以使用 AtomicInteger 作为可变的 final 计数器。

public void test() throws IOException {
    // Make sure the writer closes.
    try (FileWriter writer = new FileWriter("OutFile.txt") ) {
        // Use AtomicInteger as a mutable line count.
        final AtomicInteger count = new AtomicInteger();
        // Make sure the stream closes.
        try (Stream<String> lines = Files.lines(Paths.get("InFile.txt"))) {
            lines.forEach(line -> {
                        try {
                            // Annotate with line number.
                            writer.write(count.incrementAndGet() + " # " + line + System.lineSeparator());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
            );
        }
    }
}

15

这是一个很好的例子,说明你应该使用传统的for循环。虽然Files.lines()会给出一个顺序流,但是流可以无序地生成和处理,因此插入计数器并依赖它们的顺序是一个不好的习惯。如果你仍然真的想这样做,记住你可以在任何可以使用lambda的地方使用完整的匿名类。匿名类是普通类,因此可以拥有状态。

因此,在你的示例中,你可以这样做:

FileWriter writer = new FileWriter(output_file);

try (Stream<String> lines = Files.lines(Paths.get(input_file))) {
    lines.forEach(new Consumer<String>() {
        int i = 0;
        void accept(String line) {
            try {
                writer.write((i++) + " # " + line + System.lineSeparator());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    writer.close();
}

7
根据 Java doc 规定:
任何Lambda表达式中使用但未声明的局部变量、形式参数或异常参数必须是 final 或 effectively final(§4.12.4) ,否则在尝试使用时会引发编译时错误。
也就是说,您的变量必须是 final 或 effectively final。如果您想在 forEach 中添加一个计数器,可以像 OldCurumudgeon 建议的那样使用 AtomicInteger,这是我认为首选的方法。
我相信你还可以使用只有一个值 0 的数组作为计数器。请检查以下示例是否适用于您:
public void test() throws IOException {
    FileWriter writer = new FileWriter("OutFile.txt");
    final int[] count = {0};

    try (Stream<String> lines = Files.lines(Paths.get("InFile.txt"))) {
        lines.forEach(line -> {
            try {
                count[0]++;
                writer.write(count[0] + " # " + line + System.lineSeparator());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        );
        writer.close();
    }
}

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