Kotlin «out» и «in» и generics – правильное использование

Я пытался создать универсальную функцию сохранения данных неимущего человека, которая бы взяла MutableSet класса данных и сериализовала его на диск. Я хотел бы что-то легкое для прототипирования, и я нормально называю «save ()» в наборе настолько часто, что, если мой процесс будет убит, я могу позже возобновить с «load ()» сохраненных записей.

Но я не совсем понимаю различия между «*», «in», «out» и «Nothing» даже после перечитывания страницы Generics . Это SEEMS, чтобы работать без ошибок, но я не понимаю, почему с ними оба «выходят», я думал, что нужно быть «в» … или, скорее всего, я понимаю, что 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() 

    Ваш код содержит непроверенный список as Collection<Nothing> .

    Создание неконтролируемого акта – это способ рассказать компилятору, что вы знаете больше о типах, чем он, что позволяет вам нарушать некоторые ограничения, в том числе введенные разбросом генериков.

    Если вы удалите непроверенный бросок и оставите только проверенную его часть, т.е.

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

    Компилятор не позволит вам добавлять элементы в this.addAll(loaded) . В принципе, непроверенный бросок, который вы сделали, является грязным взломом, потому что тип Nothing не имеет реальных значений в Котлине, и вы не должны притворяться, что это так. Он работает только потому, что 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. Это делает компилятор встроенным объявленным / inferred типом на узлах 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 .

    Intereting Posts