Kotlin "из" и "в" и дженерики - правильное использование
Я пытался создать универсальную функцию сохранения данных бедного человека, которая бы взяла MutableSet класса данных и сериализовала его на диск. Я хотел бы что-то простое для создания прототипов, и я в порядке, вызывая "save()" на наборе так часто, что, если мой процесс будет убит, я смогу позже возобновить "load()" из сохраненных записей.
Но я не совсем понимаю разницу между "*", "in", "out" и "Nothing" даже после перечитывания страницы Generics. Это кажется, чтобы работать без ошибок, но я не понимаю, почему, когда они оба "вне", я думал, что нужно быть "внутри"... или, скорее всего, я полностью неверно понимаю 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 ответ
Ваш код содержит непроверенный актерский состав 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>())
}
}
}
Если вы хотите, чтобы он работал с наборами, которые содержат больше конкретных предметов, чем Serializable
или же Any
Вы можете сделать это с параметрами типа reified. Это делает компилятор встроенным объявленным / выведенным типом в 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
линия.