Как вставить счетчик в поток <String>.forEach()?

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(), но я не могу заставить его работать. Я просто не знаю, где положить я ++; в код, случайным образом закручивая, пока не помогли.

Ответы

Ответ 1

Это хороший пример того, где вы должны использовать хороший старомодный цикл. В то время как Files.lines() специально предоставляет последовательный поток, потоки могут быть созданы и обработаны не по порядку, поэтому вставка счетчиков и использование их порядка - довольно плохая привычка. Если вы все еще действительно хотите это сделать, помните, что везде, где вы можете использовать лямбда, вы все равно можете использовать полный анонимный класс. Анонимные классы являются нормальными классами и могут как таковые иметь состояние.

Итак, в вашем примере вы можете сделать следующее:

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();
}

Ответ 2

Вы можете использовать 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();
                        }
                    }
            );
        }
    }
}

Ответ 3

Как указано в Java doc:

Используется любая локальная переменная, формальный параметр или параметр исключения, но не объявленные в выражении лямбда, должны быть либо объявлены окончательными, либо быть эффективно окончательным (§4.12.4), или возникает ошибка времени компиляции используется попытка.

Это означает, что ваша переменная должна быть окончательной или окончательной. Вы хотите добавить счетчик в forEach, и для этого вы можете использовать AtomicInteger, как было предложено OldCurumudgeon, что является предпочтительным методом IMO.

Я считаю, что вы также можете использовать массив, имеющий только одно значение 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();
    }
}