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

2023-09-07 01:22:43 作者:云朵有点甜

我有一些代码:

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

如果我在过滤器末尾调用 collect(Collectors.toList()),我会得到:

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

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

If I don't do this, I get the f with the type [error: Error] in my for-loop.

推荐答案

更新: 这个问题现在在 Kotlin 1.0.1 中得到修复(之前是 KT-5190).不需要变通.

UPDATE: This issue is now fixed in Kotlin 1.0.1 (previously was KT-5190). No work around is needed.

解决方法 #1:

创建这个扩展函数,然后在 Stream 上将其简单地用作 .toList():

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>())

用法:

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

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

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!?!).

解决方法 #2:

将正确的类型参数添加到您的调用中作为 Collectors.toList<Path>() 以避免该参数的类型推断:

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>())

但解决方法 #1 中的扩展功能更可重用且更简洁.

But the extension function in workaround #1 is more re-usable and more concise.

解决该错误的另一种方法是不收集 Stream.你可以保持懒惰,将 Stream 转换为 Kotlin SequenceIterator,这是一个用于制作 Sequence:

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()

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

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.

当然,在 Sequence 的某些处理结束时,您可以 toList()toSet() 或使用任何其他用于更改集合类型的 Kotlin 扩展.

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.

有了这个,我将创建一个用于列出文件的扩展,以避免 PathsPathFiles、文件:

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

使用 Java 8 流的替代方案:

我们可以稍微更改您的代码以从 File 获取文件列表,而您只需在末尾使用 toList():

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()

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

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

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.

一般:

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

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.

另请参阅:

标准 Kotlin 库中有哪些 Java 8 Stream.collect 等效项? - 你会发现它更简洁使用 Kotlin 标准库函数和扩展.Kotlin 集合和扩展函数 API 文档Kotlin 序列 API 文档Kotlin 成语 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

您应该查看 API 参考以了解什么在标准库中可用.

You should review the API reference for knowing what is available in the stdlib.