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

时间:2016-03-01 11:21:18

标签: java java-8 java-stream kotlin

我有一些代码:

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]

2 个答案:

答案 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 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或集合的函数开始来避免这种情况。

一般情况:

在大多数情况下,您可以避免使用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()