Kotlin "out" 和 "in" 以及泛型——正确使用

Kotlin "out" and "in" and generics - proper usage

我试图制作一个通用的穷人数据持久化函数,该函数需要一个 MutableSet of data class并将其序列化到磁盘。我想要一些简单的原型设计,我可以经常调用 "save()" 设置,这样如果我的进程被杀死,我可以稍后使用 "load()" 保存的条目恢复。

但即使在重新阅读 Generics 页面后,我也不太明白“*”、'in'、'out' 和 'Nothing' 之间的区别。这似乎可以正常工作而不会引发错误,但我不明白为什么它们都是 "out",我认为其中一个必须是 "in"... 或者更可能我完全理解 Kotlin Generics错误的。有正确的方法吗?

/** Save/load any MutableSet<Serializable> */
fun MutableSet<out Serializable>.save(fileName:String="persist_${javaClass.simpleName}.ser") {
    val tmpFile = File.createTempFile(fileName, ".tmp")
    ObjectOutputStream(GZIPOutputStream(FileOutputStream(tmpFile))).use {
        println("Persisting collection with ${this.size} entries.")
        it.writeObject(this)
    }
    Files.move(Paths.get(tmpFile), Paths.get(fileName), StandardCopyOption.REPLACE_EXISTING)
}

fun MutableSet<out Serializable>.load(fileName:String="persist_${javaClass.simpleName}.ser") {
    if (File(fileName).canRead()) {
        ObjectInputStream(GZIPInputStream(FileInputStream(fileName))).use {
            val loaded = it.readObject() as Collection<Nothing>
            println("Loading collection with ${loaded.size} entries.")
            this.addAll(loaded)
        }
    }
} 

data class MyWhatever(val sourceFile: String, val frame: Int) : Serializable

然后可以使用

启动任何应用程序
val mySet = mutableSetOf<MyWhatever>()
mySet.load()

您的代码包含未经检查的转换 as Collection<Nothing>

进行未经检查的强制转换是一种告诉编译器您比它更了解类型的方式,允许您违反一些限制,包括由泛型方差引入的限制。

如果您删除未选中的演员表并仅保留其中已选中的部分,即

val loaded = it.readObject() as Collection<*> 

编译器不允许您在 this.addAll(loaded) 行中添加项目。 基本上,您所做的未经检查的转换是一种肮脏的 hack,因为 Nothing 类型在 Kotlin 中没有实际值,您不应该假装它有。它之所以起作用,是因为 MutableSet<out Serializable> 同时意味着 MutableSet<in Nothing>(这意味着实际类型参数被擦除——它可以是 Serializable 的任何子类型——而且因为不知道是什么正是集合的项目类型,没有什么可以安全地放入集合中。

实现第二​​个函数的类型安全方法之一是:

fun MutableSet<in Serializable>.load(
    fileName: String = "persist_${javaClass.simpleName}.ser"
) {
    if (File(fileName).canRead()) {
        ObjectInputStream(GZIPInputStream(FileInputStream(fileName))).use {
            val loaded = it.readObject() as Collection<*>
            println("Loading collection with ${loaded.size} entries.")
            this.addAll(loaded.filterIsInstance<Serializable>())
        }
    }
}

如果你想让它与包含比 SerializableAny 更多具体项目的集合一起工作,你可以使用具体化的类型参数来实现。这使得编译器在 load 调用站点内联 declared/inferred 类型,以便该类型传播到 filterIsInstance 并正确检查项目:

inline fun <reified T> MutableSet<in T>.load(
    fileName: String = "persist_${javaClass.simpleName}.ser"
) {
    if (File(fileName).canRead()) {
        ObjectInputStream(GZIPInputStream(FileInputStream(fileName))).use {
            val loaded = it.readObject() as Collection<*>
            println("Loading collection with ${loaded.size} entries.")
            this.addAll(loaded.filterIsInstance<T>())
        }
    }
}

或以其他更适合您的方式检查项目。例如。 loaded.forEach { if (it !is T) throw IllegalArgumentException() }addAll 行之前。