我有一些代码:
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-loop中获得f
类型[error: Error]
。
答案 0 :(得分:12)
更新: 此问题现已在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 Sequence
或Iterator
,这是制作Sequence
的扩展函数:
fun <T: Any> Stream<T>.asSequence(): Sequence<T> = this.iterator().asSequence()
现在您有forEach
和许多其他功能可供您使用,同时仍然懒得只使用Stream
一次。使用myStream.iterator()
是另一种方式,但可能没有Sequence
那么多的功能。
当然,在Sequence
的某些处理结束时,您可以toList()
或toSet()
或使用任何其他Kotlin扩展来更改集合类型。
有了这个,我会为列出文件创建一个扩展,以避免Paths
,Path
,Files
,File
的错误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) }
我们可以稍微更改您的代码,以便从File
获取文件列表,您最后只需使用toList()
:
file.listFiles().filter { /* filter clause */ }.toList()
或
file.listFiles { file, name -> /* filter clause */ }.toList()
不幸的是,您最初使用的Files.list(...)
会返回Stream
并且不会让您有机会使用传统的集合。此更改通过从返回Array或集合的函数开始来避免这种情况。
一般情况:
在大多数情况下,您可以避免使用Java 8流,并使用本机Kotlin stdlib函数和Java集合的扩展。 Kotlin确实使用Java集合,通过编译时只读和可变接口。但随后它增加了扩展功能以提供更多功能。因此,您具有相同的性能,但具有更多功能。
另见:
您应该查看API reference以了解stdlib中的可用内容。
答案 1 :(得分:2)
这是另一项工作,如果你出于某种原因坚持使用较旧的测试版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()