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

Ben*_*n H 3 generics kotlin kotlin-generics

我试图让一个普通的可怜人的数据持久化功能,将采取一个MutableSet数据类,并将其序列化到磁盘.我想要一些简单的原型设计,并且可以经常在集合上调用"save()",这样如果我的进程被杀死,我可以稍后使用已保存条目的"load()"恢复.

但即使重读了Generics页面,我也没有完全区分'*','in','out'和'Nothing'之间的区别.这个SEEMS可以在不抛出错误的情况下工作,但我不明白为什么他们都"出局",我认为一个人必须"进入"...或者更可能我理解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
Run Code Online (Sandbox Code Playgroud)

然后能够启动任何应用程序

val mySet = mutableSetOf<MyWhatever>()
mySet.load()
Run Code Online (Sandbox Code Playgroud)

hot*_*key 6

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

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

如果你删除未经检查的强制转换并只留下它的已检查部分,即

val loaded = it.readObject() as Collection<*> 
Run Code Online (Sandbox Code Playgroud)

编译器不允许您添加行中的项目this.addAll(loaded).基本上,你做的未经检查的演员是一个肮脏的黑客,因为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>())
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你想将其与容纳更多的具体项目比台工作Serializable或者Any,你可以用物化的类型参数做到这一点.这使得编译器在load调用站点内联声明/推断类型,以便将类型传播到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>())
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者以另一种更适合您的方式检查物品.例如在线loaded.forEach { if (it !is T) throw IllegalArgumentException() }之前addAll.