Streams: Zeilennummern
Mit Java eine Datei zu lesen und in einen Stream zu vrewandeln ist mit Java extrem einfach und erlaubt kompakte Verarbeitung. Hier wird eine Datei gelesen, die Zeilen in Objekte verpackt und diese gespeichert:
private List<Record> content; public void read(Path file) { try (Stream<String> stream = Files.lines(file, ENCODING)) { content = stream.map(this::toRecord).collect(Collectors.toList()); } catch (IOException e) { errors.add(e.getMessage()); } } } private Record toRecord(String line) { // ... }
Was aber, wenn die Record-Objekte die Nummer der Zeile in der Datei enthalten sollen? Das geht völlig am Konzept des Streams vorbei, kann aber – wenn's nicht zu streng zugeht – mit Java-Bordmitteln recht einfach hingebogen werden.
Nehemen wir an, Record
hätte eine Methode setLineNum(int num)
die die Zeilen-Nummer setzt und basteln – ganz naiv – das hier:
public void read (Path file) { try (Stream<String> stream = Files.lines(file, ENCODING)) { int num = 1; content = stream.map(this::toRecord) // .peek(r -> r.setLineNum(num++)) // .collect(Collectors.toList()); } catch (IOException e) { errors.add(e.getMessage()); } } }
Dann haut uns das der Compiler um die Ohren, denn alle Variablen die in Lambda-Ausdrücken gebraucht werden müssen "effectively immutable" sein. Wir versuchen num bei jedem Objekt im Stream zu verändern und das darf nicht sein.
Also kapseln wir die Inkrementierung einfach in ein Objekt. Was innerhalb des Objekts geschieht, geht den Compiler nichts an...
private static class Counter { private long number = 1; public long inc() { return number++; } }
Jetzt können wir einen Counter erzeugen und verwenden:
public void read (Path file) { try (Stream<String> stream = Files.lines(file, ENCODING)) { Counter ctr = new Counter(); content = stream.map(this::toRecord) // .peek(r -> r.setLineNum(ctr.inc())) // .collect(Collectors.toList()); } catch (IOException e) { errors.add(e.getMessage()); } } }