Kotlin has nice wrappers and shortcuts, but sometimes I get caught not understanding them.
I have this simplified code:
class PipeSeparatedItemsReader (private val filePath: Path) : ItemsReader {
override fun readItems(): Sequence<ItemEntry> {
return filePath.useLines { lines ->
lines.map { ItemEntry("A","B","C","D",) }
}
}
And then I have:
val itemsPath = Path(...).resolve()
val itemsReader = PipeSeparatedItemsReader(itemsPath)
for (itemEntry in itemsReader.readItems())
updateItem(itemEntry)
// I have also tried itemsReader.readItems().forEach { ... }
Which is quite straightforward - I expect this code to give me a sequence which opens a file and reads the lines, parses them, and gives ItemEntry
s, and when used up, close the file.
What I get, however, is IOException("Stream closed")
.
Somehow, even before the first item is read (I have debugged), somewhere within Kotlin's wrappers, the reader.in
becomes null, so this exception is thrown in hasNext()
.
I have seen a similar question here: Kotlin to chain multiple sequences from different InputStream?
That one includes a lot of Java boilerplate which I would like to avoid.
How should I code this sequence using Path.useLines()
?
CodePudding user response:
Every Kotlin helper with "use" in the name closes the underlying resource at the end of the lambda you pass (at least that's a convention in the stdlib as far as I know). The most common example being AutoCloseable.use.
The Path.useLines extension is no exception:
Calls the block callback giving it a sequence of all the lines in this file and closes the reader once the processing is complete. [emphasis mine]
This means useLines
closes the sequence of lines once the block is done, and thus you cannot return a lazy sequence out of it because you can't use it after the useLines
function returns.
So, if you want to return a sequence of lines for later use, you cannot return a transformed sequence from that of useLines
directly. Sequences actually cannot detect when someone is done using them, hence why useLines
needs a lambda to give a "scope" or "lifetime" to the sequence and know when to close the underlying reader.
If you want to wrap this, you have 2 major options: either split the sequence operation and the close operation (make your PipeSeparatedItemsReader
closeable), or use a lambda to process things in-place in readItems()
the same way useLines
does.