Kotlin“out”和“in”以及泛型 – 正确的用法

我试图做一个通用的穷人的数据持久化功能,将采取MutableSet数据类和序列化到磁盘。 我想要一个简单的原型设计,并且可以每隔一段时间在设备上调用“save()”,这样如果我的进程被终止了,我可以稍后恢复保存条目的“load()”。

但是,即使在重新读取泛型页面之后,我也不太清楚'*','in','out'和'Nothing'之间的区别。 这SEEMS的工作没有抛出错误,但我不明白为什么他们都“出”,我认为必须是“在”…或者更可能我是理解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() 

您的代码包含一个未经检查的强制类型转换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行之前loaded.forEach { if (it !is T) throw IllegalArgumentException() }