如何在Kotlin中的Java 8 Stream上调用collect(Collectors.toList())?

NCN*_*ros 6 java java-8 kotlin java-stream

我有一些代码:

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我collect(Collectors.toList())在过滤器结束时调用,我得到:

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
Run Code Online (Sandbox Code Playgroud)

如果我不这样做,我会得到我的for循环中f的类型[error: Error].

Jay*_*ard 12

更新:此问题现已在Kotlin 1.0.1中修复(之前为KT-5190).无需解决问题.


解决方法

解决方法#1:

创建这个扩展功能,然后用它作为简单.toList()Stream:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())
Run Code Online (Sandbox Code Playgroud)

用法:

Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()
Run Code Online (Sandbox Code Playgroud)

这增加了一个更明确的通用参数来Collectors.toList()调用,防止其仿制药的推理过程中出现的bug(这是有点令人费解了该方法的返回类型Collector<T, ?, List<T>>,eeeks!?! ).

解决方法#2:

在调用中添加正确的类型参数,Collectors.toList<Path>()以避免该参数的类型推断:

Files.list(Paths.get(file)).filter { /* filter clause */ }.collect(Collectors.toList<Path>())
Run Code Online (Sandbox Code Playgroud)

但是变通方法#1中的扩展功能更易于使用且更简洁.


保持懒惰

另一种解决方法是不收集错误Stream.你可以保持懒惰,并转换Stream为Kotlin SequenceIterator,这是一个扩展功能,使Sequence:

fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用forEach许多其他功能,同时仍然Stream只使用懒惰而且只使用一次.使用myStream.iterator()是另一种方式,但可能没有那么多的功能Sequence.

当然,在一些处理结束后,对的Sequence,你可以toList()或者toSet()或使用任何其他的科特林扩展的改变集合类型.

而与此,我想创建一个扩展列表文件,以避免不好的API设计Paths,Path,Files,File:

fun Path.list(): Sequence<Path> = Files.list(this).iterator().asSequence()
Run Code Online (Sandbox Code Playgroud)

哪个至少从左到右流动得很好:

File(someDir).toPath().list().forEach { println(it) }
Paths.get(dirname).list().forEach { println(it) }
Run Code Online (Sandbox Code Playgroud)

使用Java 8 Streams的替代方法:

我们可以稍微更改您的代码以获取文件列表,File而您最后会使用toList()它:

file.listFiles().filter { /* filter clause */ }.toList()
Run Code Online (Sandbox Code Playgroud)

要么

file.listFiles { file, name ->  /* filter clause */ }.toList()
Run Code Online (Sandbox Code Playgroud)

不幸的是Files.list(...),您最初使用的是返回a Stream,并且不会让您有机会使用传统的集合.此更改通过从返回Array或集合的函数开始来避免这种情况.

一般来说:

在大多数情况下,您可以避免使用Java 8流,并使用本机Kotlin stdlib函数和Java集合的扩展.Kotlin确实使用Java集合,通过编译时只读和可变接口.但随后它增加了扩展功能以提供更多功能.因此,您具有相同的性能,但具有更多功能.

也可以看看:

您应该查看API参考以了解stdlib中可用的内容.