我有一些代码:

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")
    }
}

如果我在过滤器的末尾调用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

如果我不这样做,则会在for循环中获得类型为f[error: Error]

最佳答案

更新:此问题现在已在Kotlin 1.0.1(以前是KT-5190)中得到修复。无需任何解决。

解决方法

解决方法#1:

创建此扩展功能,然后将其简单地用作.toList()上的Stream:

fun <T: Any> Stream<T>.toList(): List<T> = this.collect(Collectors.toList<T>())

用法:
Files.list(Paths.get(file)).filter { /* filter clause */ }.toList()

这会在Collectors.toList()调用中添加一个更明确的泛型参数,从而防止了在推断泛型时发生的错误(对于该方法的返回类型Collector<T, ?, List<T>>,eeeks!?!有点费解。)。

解决方法2:

将正确的类型参数作为Collectors.toList<Path>()添加到您的调用中,以避免对该参数的类型推断:
Files.list(Paths.get(file)).filter { /* filter clause */ }.collect(Collectors.toList<Path>())

但是变通办法#1中的扩展功能更可重用且更简洁。

保持懒惰

解决该错误的另一种方法是不收集Stream。您可以保持懒惰,并将Stream转换为Kotlin SequenceIterator,这是制作Sequence的扩展功能:
fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()

现在,您可以使用forEach和许多其他功能,同时仍然懒惰地仅使用一次Stream。使用myStream.iterator()是另一种方法,但可能没有Sequence那样多的功能。

当然,在对Sequence进行某些处理之后,您可以toList()toSet()或使用其他Kotlin扩展名来更改集合类型。

有了这个,我将创建一个列出文件的扩展名,以避免PathsPathFilesFile的API设计不佳:
fun Path.list(): Sequence<Path> = Files.list(this).iterator().asSequence()

至少会从左到右顺畅地流动:
File(someDir).toPath().list().forEach { println(it) }
Paths.get(dirname).list().forEach { println(it) }

使用Java 8 Streams的替代方法:

我们可以略微更改您的代码,以从File获取文件列表,而您只需在末尾使用toList():
file.listFiles().filter { /* filter clause */ }.toList()

或者
file.listFiles { file, name ->  /* filter clause */ }.toList()

不幸的是,您最初使用的Files.list(...)返回了Stream,但没有给您使用传统集合的机会。此更改通过从返回Array或collection的函数开始避免了这种情况。

一般而言:

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

另请参见:
  • What Java 8 Stream.collect equivalents are available in the standard Kotlin library?-您将看到使用Kotlin stdlib函数和扩展更为简洁。
  • Kotlin Collections, and Extension Functions API docs
  • Kotlin Sequences API docs
  • Kotlin idioms

  • 您应该查看API reference以了解stdlib中的可用内容。

    09-10 02:22
    查看更多