I have a text file which I read in as a list of strings. After that I try to get one of the strings, modify it and replace it within the list again.
My text file look as follows:
a/0/0/0/0
b/0/0/0/0
c/0/0/0/0
d/0/0/0/0
The code look as follows:
String startOfLine = "b";
List<String> lines = new ArrayList<>();
try (Stream<String> stream = Files.lines(Paths.get("pathToMyTextFile.txt"))) {
lines = stream.toList();
} catch (IOException e) {
e.printStackTrace();
}
int index = 0;
int count = 0;
String modifiedString = "";
for (String s:lines) {
count ;
if (s.startsWith(startOfLine)){
String[] splitS = s.split("/");
int increment = Integer.parseInt(splitS[2]) 1;
modifiedString =
splitS[0] "/"
splitS[1] "/"
increment "/"
splitS[3] "/"
splitS[4];
index = count;
}
}
lines.set(index, modifiedString);
What I've tried to do is to get the string within the list that starts with the letter "b".
Increase the "0"
after the second "/"
to "1"
and then put the string together again to replace the old value within the list.
What happens is that IntelliJ (which I'm using) complains about an immutable object is being modified.
And I'm almost certain that this should be obtainable within the stream itself in the first place, but can't seem to pull it off on my own. Would it be possible to change the value of the line that starts with b
within the stream before converting the stream to a list?
CodePudding user response:
Terminal operation toList()
generates an array, which is being transformed into a List
, and then wrapped by and UnmodifiableList
generated by the is being with
default List<T> toList() {
return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray())));
}
UnmodifiableList
overrides all mutating behavior, including set()
method, so that it would throw an UnsupportedOperationException
.
The only way you can ensure that collection produced from the stream is modifiable is to make use of Collectors.toCollection()
:
List<String> lines = Collections.emptyList();
try (Stream<String> stream = Files.lines(Paths.get("pathToMyTextFile.txt"))) {
lines = stream.collect(Collectors.toCollection(ArrayList::new));
} catch (IOException e) {
e.printStackTrace();
}
Alternatively, to process the data right in the Stream you can make use of the Java 16 operation mapMulti()
which allows to incorporate imperative logic into a stream:
List<String> lines = Collections.emptyList();
try (Stream<String> stream = Files.lines(Paths.get("pathToMyTextFile.txt"))) {
lines = processLines(stream, startOfLine);
} catch (IOException e) {
e.printStackTrace();
}
public static List<String> processLines(Stream<String> lines, String startOfLine) {
return lines
.<String>mapMulti((line, consumer) -> {
if (!line.startsWith(startOfLine)) consumer.accept(line);
else {
String[] parts = line.split("/");
parts[2] = String.valueOf(Integer.parseInt(parts[2]) 1);
consumer.accept(String.join("/", parts));
}
})
.toList();
}
For versions of JDK earlier than 16 the old-good map()
operation can be used instead of mupMalti()
, but in such case it would be cleaner to introduce one more delegating method.
Sidenotes: Files.lines().toList()
is an equivalent of Files.readAllLines()
which produces a List
(note that it's either not guaranteed to be modifiable).