3

我试图制作一个通用的穷人数据持久性函数,它将获取一个MutableSet数据类并将其序列化到磁盘。我想要一些易于原型制作的东西,并且可以每隔一段时间在集合上调用“save()”,这样如果我的进程被杀死,我以后可以使用已保存条目的“load()”恢复。

但即使在重新阅读泛型页面后,我也不太明白“*”、“输入”、“输出”和“无”之间的区别。这似乎可以正常工作而不会引发错误,但我不明白为什么它们都“出局”,我认为必须“入局”......或者更有可能我完全错误地理解了 Kotlin 泛型。有这样做的正确方法吗?

/** 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()
4

1 回答 1

6

您的代码包含未经检查的演员表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>())
        }
    }
}

如果您想让它与包含更多具体项目的集合一起使用Serializableor 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>())
        }
    }
}

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

于 2017-10-20T18:11:57.147 回答