Skip to content

Use Java streams where applicable - #4

Open
jingyucodes wants to merge 5 commits into
masterfrom
branch-A-Streams
Open

jingyucodes wants to merge 5 commits into
masterfrom
branch-A-Streams

Conversation

@jingyucodes

Copy link
Copy Markdown
Owner

Summary

  • Echo#executeCommand()'s on/find cases: replace manual filter-and-accumulate loops with stream().filter(...).collect(Collectors.toList()).
  • Storage#save(): replace the manual task-to-line loop with stream().map(Task::toFileFormat).collect(Collectors.toList()).
  • Storage#load(): replace the filter/parse/filter loop with a single filter -> map -> filter stream pipeline.

Streams were applied only where the existing loop was a plain filter/map/collect with no early exit, branching, or meaningful side effects. Left as plain loops (and noted in the first commit's message): Ui's numbered task listings (need the running index), Echo#run()'s console read loop (stateful I/O with an early break), and DialogBox#flip()'s Collections.reverse() call (already a single clean statement).

Test plan

  • ./gradlew compileJava checkstyleMain passes
  • Manually exercise todo/deadline/event/mark/find/on, then reload and confirm the save file round-trips correctly

The "on" and "find" cases in executeCommand() each built a matches
list by looping over every task and manually adding the ones that
passed a single predicate (occursOn()/matchesKeyword()) — the
classic filter-then-collect shape streams exist for, with no early
exit, no branching, and no side effects beyond building the result.

Replace each loop with tasks.getAll().stream().filter(...)
.collect(Collectors.toList()), which states the intent (keep tasks
matching this predicate) directly instead of via a manual
accumulator loop.

Other loops in the codebase (e.g. Ui's numbered task listings,
Echo's console read loop) are intentionally left as-is: they either
need the running index for output (streams don't index cleanly) or
involve stateful, sequential I/O, neither of which streams express
more clearly than a plain loop.
save() built its list of save-file lines by looping over tasks and
adding task.toFileFormat() for each one — a plain 1-to-1
transformation with no filtering, branching, or side effects, which
is exactly what Stream#map exists to express.

Replace the loop with tasks.stream().map(Task::toFileFormat)
.collect(Collectors.toList()), naming the operation (map) instead
of spelling out the accumulator loop that implements it.
load() looped over every line, skipped blanks with a bare continue,
parsed the rest, then conditionally added successfully-parsed
tasks. That's a filter (skip blank lines) -> map (parse) -> filter
(drop failed parses) pipeline expressed as a loop with a mutable
accumulator and a continue, which streams state more directly.

Rewrite it as Files.readAllLines(filePath).stream()
.filter(line -> !line.isBlank()).map(this::parseLine)
.filter(Objects::nonNull).collect(Collectors.toList()), returning
the stream's result directly instead of building it up in a
pre-declared list. parseLine() still prints its own warning for a
corrupted line as a side effect inside map(); that's unchanged from
before and kept for the same reason (surface the problem without
aborting the whole load).
# Conflicts:
#	src/main/java/echo/storage/Storage.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant