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 линия.

Другие вопросы по тегам