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

I need to write the line with the line number, so I tried to add a counter into the .forEach(), but I can't get it to work. I just don't know where to put the i++; into the code, randomly screwing around didn't help so far.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Vega
  • 2,661
  • 5
  • 24
  • 49
  • 1
    Declare an int outside of the loop, set it to 0, and increase it each iteration. – Stultuske May 13 '15 at 09:45
  • @Stultuske Does that work? Have you tried it? Note that there isn't any loop here, but there is a lambda. – user253751 May 13 '15 at 09:48
  • That's the question, I have a int i = 0; outside of the lambda, but I can't get it to increase in each for.Each() cycle. Stultuske neither read the code nor the question, only the topic. – Vega May 13 '15 at 09:49
  • 1
    did you try writer.write(i++ + " # " + line + System.lineSeparator()); – Veselin Davidov May 13 '15 at 09:50
  • I don't have Java 8 installed here, so couldn't try it out. but there should be a way to get that working. – Stultuske May 13 '15 at 09:50
  • @Veselin Davidov Then I get "local variable defined in an enclosing scope must be final or effectively final" – Vega May 13 '15 at 09:51
  • create a final instance of a class Counter, which has 1 variable, an int, set to 0. increase that instance of Counter's counter each time you iterate. final for Objects means you can't re-reference, it doesn't mean you can't change it's value (unless you declare that variable within Counter as final as well) – Stultuske May 13 '15 at 09:52

3 Answers3

59

You can use an AtomicInteger as a mutable final counter.

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();
                        }
                    }
            );
        }
    }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
15

This is a good example of where you should rather use a good old fashioned for loop. While Files.lines() specifically gives a sequential stream, streams can be produced and processed out of order, so inserting counters and relying on their order is a rather bad habit to get into. If you still really want to do it, remember that anywhere you can use a lambda you can still use a full anonymous class. Anonymous classes are normal classes, and can as such have state.

So in your example you could do like this:

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();
}
Erik Vesteraas
  • 4,675
  • 2
  • 24
  • 37
7

As stated in Java doc:

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

It means your variable has to be final or effectively final. You want to add a counter into forEach and for that you can use an AtomicInteger as suggested by OldCurumudgeon which is IMO a preferred approach.

I believe you can also use an array having only one value 0 which you can use as counter. Check and let me know if the following example works for you:

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();
    }
}
akhil_mittal
  • 23,309
  • 7
  • 96
  • 95