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

我有一些代码:

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循环中得到类型为[error: Error]f

更新:此问题现已在Kotlin 1.0.1中解决(以前是KT-5190 )。 没有工作需要。


解决方法

解决方法#1:

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

 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其他许多功能,但仍然只能使用一次。 使用myStream.iterator()是另一种方法,但可能没有Sequence功能。

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

与此,我会创建列表文件的扩展名,以避免不良的API设计的PathsPathFilesFile

 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 ,并没有给你机会使用传统的集合。 这个改变避免了通过一个返回一个数组或集合的函数开始。

一般来说:

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

也可以看看:

  • 什么Java 8 Stream.collect等价物在标准Kotlin库中可用? – 你会看到使用Kotlin stdlib函数和扩展更简洁。
  • Kotlin集合和扩展函数API文档
  • Kotlin序列API文档
  • Kotlin成语

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

这是另一个解决方法,如果你出于某种原因停留在Kotlin的较旧的测试版上,需要一个更残酷的解决方法…

解决方法#3 🙁 丑陋,旧的解决方法只适用于老版本的Kotlin)

将这个Java类添加到您的项目中:

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

而这一个Kotlin扩展功能:

 fun <T: Any> Stream<T>.toList(): List<T> = CollectFix.streamToList(this) 

然后任何时候你有这种情况下,使用这个新的扩展:

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