I get used to using lambda to parse file line by line(much neater than bufferedReader.readLine()
) for a long time. But today I faced a problem: add a line number to each line.
It need a counter, but the variable in lambda should be effectively final. Finally, I hacked it with an int array.
Code:
public static void main(String[] args) {
int[] counter = new int[1];
counter[0] = 0;
try (Stream<String> lines = Files.lines(Paths.get("/tmp/timeline.txt"), Charset.defaultCharset())) {
lines.limit(10).forEachOrdered(line -> {
line = line.trim();
counter[0] ++;
System.out.println("Line " + counter[0] + ": " + line);
});
} catch (IOException e) {
e.printStackTrace();
}
}
Output:
Line 1: p 5714026 wEkQ
Line 2: v 8235889
Line 3: v 6534726
...
My question is, how to avoid my hack and solve that problem elegantly?
There is no elegant functional solution to a non-functional task. The first you may consider, is just resorting to an ordinary anonymous inner class:
String path = "/tmp/timeline.txt";
try(Stream<String> lines = Files.lines(Paths.get(path), Charset.defaultCharset())) {
lines.limit(10).forEachOrdered(new Consumer<String>() {
int counter = 0;
public void accept(String line) {
System.out.println("Line " + counter++ + ": " + line.trim());
}
});
} catch (IOException e) {
e.printStackTrace();
}
The advantage is that it doesn’t pretend to be functional where it isn’t and the scope of the counter
variable has the smallest scope needed for this task.
If you are going to do more than just-printing these numbered lines and need a solution compatible with all stream operations, re-implementing the stream source is a straight-forward solution:
static Stream<String> numberedLines(Path path, Charset cs) throws IOException {
BufferedReader br = Files.newBufferedReader(path, cs);
return StreamSupport.stream(new Spliterators.AbstractSpliterator<String>(
Long.MAX_VALUE, Spliterator.ORDERED|Spliterator.NONNULL) {
int counter;
public boolean tryAdvance(Consumer<? super String> action) {
String line;
try {
line = br.readLine();
if(line==null) return false;
action.accept("Line " + counter++ + ": " + line.trim());
return true;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}, true).onClose(()->{ try { br.close(); }
catch (IOException ex) { throw new UncheckedIOException(ex); }
});
}
Of course, this isn’t as simple as a single lambda expression but using this reusable method, you can use all stream operations without problems, e.g.
String path = "/tmp/timeline.txt";
try(Stream<String> lines = numberedLines(Paths.get(path), Charset.defaultCharset())) {
lines.skip(10).limit(10).forEachOrdered(System.out::println);
} catch(IOException e) {
e.printStackTrace();
}
You might use an AtomicInteger
1 like
AtomicInteger ai = new AtomicInteger();
// ...
lines.limit(10).forEachOrdered(line -> {
System.out.printf("Line %d: %s%n", ai.incrementAndGet(), line.trim());
});
1And I would prefer formatted IO with printf
to using String
concatenation.
I would implement a Function
to number the lines :
public static class LineNumberer implements Function<String,String> {
private int lineCount;
public lineNumberer() { lineCount = 0; }
public String apply(String in) {
return String.format("%d %s", lineCount++, in);
}
}
public static void main (String[] args) throws java.lang.Exception
{
Files.lines(Paths.get("/tmp/timeline.txt")).map(new LineNumberer()).forEach(System.out::println);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With