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

时间:2017-10-20 17:27:40

标签: generics kotlin kotlin-generics

我试图制作一个普通的穷人数据持久性功能,它可以采用数据类 MutableSet 并将其序列化为磁盘。我喜欢简单的原型设计,我可以打电话" save()"每隔一段时间就设置一次,这样如果我的进程被杀死,我可以稍后用#34; load()"已保存的条目。

但我并没有完全区分' *',''' out'和' Nothing&# 39;甚至在重新阅读Generics页面之后。这个SEEMS可以在不抛出错误的情况下工作,但是我不知道为什么他们都是" out",我认为一个人必须是"在" ...或更多我很可能完全错误地理解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()

1 个答案:

答案 0 :(得分:6)

您的代码包含未经检查的广告as Collection<Nothing>

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

如果你删除未经检查的演员表,只留下它的部分,即

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

编译器不允许您添加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>())
        }
    }
}

如果您想使用包含比SerializableAny更具体的项目的集合,您可以使用具体的类型参数。这使得编译器在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>())
        }
    }
}

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