I have the some code:
directoryChooser.title = "Select the directory"
val file = directoryChooser.showDialog(null)
if (file != null) {
var files = Files.list(file.toPath())
.filter { f ->
f.fileName.endsWith("zip") && f.fileName.endsWith("ZIP")
&& (f.fileName.startsWith("1207") || f.fileName.startsWith("4407") || f.fileName.startsWith("1507") || f.fileName.startsWith("9007") || f.fileName.startsWith("1807"))
}
for (f in files) {
textArea.appendText(f.toString() + "\n")
}
}
If I call collect(Collectors.toList())
at the end of filter, I get:
Error:(22, 13) Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: no descriptor for type constructor of ('Captured(in ('Path'..'Path?'))'..'CapturedTypeConstructor(in ('Path'..'Path?'))?')
Cause: no descriptor for type constructor of ('Captured(in ('Path'..'Path?'))'..'CapturedTypeConstructor(in ('Path'..'Path?'))?')
File being compiled and position: (22,13) in D:/My/devel/ListOfReestrs/src/Controller.kt
PsiElement: var files = Files.list(file.toPath())
.filter { f ->
f.fileName.endsWith("zip") && f.fileName.endsWith("ZIP")
&& (f.fileName.startsWith("1207") || f.fileName.startsWith("4407") || f.fileName.startsWith("1507") || f.fileName.startsWith("9007") || f.fileName.startsWith("1807"))
}.collect(Collectors.toList())
The root cause was thrown at: JetTypeMapper.java:430
If I don't do this, I get the f
with the type [error: Error]
in my for-loop.
UPDATE: This issue is now fixed in Kotlin 1.0.1 (previously was KT-5190). No work around is needed.
Workarounds
Workaround #1:
Create this extension function, then use it simply as .toList()
on the Stream
:
fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())
usage:
Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()
This adds a more explicit generic parameter to the Collectors.toList()
call, preventing the bug which occurs during inference of the generics (which are somewhat convoluted for that method return type Collector<T, ?, List<T>>
, eeeks!?!).
Workaround #2:
Add the correct type parameter to your call as Collectors.toList<Path>()
to avoid type inference of that parameter:
Files.list(Paths.get(file)).filter { /* filter clause */ }.collect(Collectors.toList<Path>())
But the extension function in workaround #1 is more re-usable and more concise.
Staying Lazy
Another way around the bug is to not collect the Stream
. You can stay lazy, and convert the Stream
to a Kotlin Sequence
or Iterator
, here is an extension function for making a Sequence
:
fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()
Now you have forEach
and many other functions available to you while still consuming the Stream
lazily and only once. Using myStream.iterator()
is another way but may not have as much functionality as a Sequence
.
And of course at the end of some processing on the Sequence
you can toList()
or toSet()
or use any other of the Kotlin extensions for changing collection types.
And with this, I would create an extensions for listing files to avoid the bad API design of Paths
, Path
, Files
, File
:
fun Path.list(): Sequence<Path> = Files.list(this).iterator().asSequence()
which would at least flow nicely from left to right:
File(someDir).toPath().list().forEach { println(it) }
Paths.get(dirname).list().forEach { println(it) }
Alternatives to using Java 8 Streams:
We can change your code slightly to get the file list from File
instead, and you would just use toList()
at the end:
file.listFiles().filter { /* filter clause */ }.toList()
or
file.listFiles { file, name -> /* filter clause */ }.toList()
Unfortunately the Files.list(...)
that you originally used returns a Stream
and doesn't give you the opportunity to use a traditional collection. This change avoids that by starting with a function that returns an Array or collection.
In General:
In most cases you can avoid Java 8 streams, and use native Kotlin stdlib functions and extensions to Java collections. Kotlin does indeed use Java collections, via compile-time readonly and mutable interfaces. But then it adds extension functions to provide more functionality. Therefore you have the same performance but with many more capabilities.
See Also:
- What Java 8 Stream.collect equivalents are available in the standard Kotlin library? - You will see it is more concise to use Kotlin stdlib functions and extensions.
- Kotlin Collections, and Extension Functions API docs
- Kotlin Sequences API docs
- Kotlin idioms
You should review the API reference for knowing what is available in the stdlib.
This is another work around if you are for some reason stuck on older beta version of Kotlin which requires a more brutal workaround...
Workaround #3: (ugly, an old workaround ONLY for old versions of Kotlin)
Add this Java class to your project:
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class CollectFix {
public static <T> List<T> streamToList(Stream<T> s) {
return s.collect(Collectors.toList());
}
}
And this one Kotlin extension function:
fun <T: Any> Stream<T>.toList(): List<T> = CollectFix.streamToList(this)
And then any time you have this case, use this new extension:
Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()