2017-10-20 42 views
2

j'ai essayé de faire une fonction des données de générique pauvre homme persistance qui prendrait un MutableSet de classe de données et sérialisation sur disque. Je voudrais quelque chose de facile pour le prototypage, et j'appelle "save()" sur l'ensemble si souvent que si mon processus est tué, je peux plus tard reprendre avec un "load()" des entrées sauvegardées. Mais je ne comprends pas les différences entre '*', 'in', 'out' et 'Nothing' même après avoir relu la page Generics. Cela semble fonctionner sans jeter d'erreurs, mais je ne comprends pas pourquoi les deux étant "out", je pensais que l'on devrait être "in" ... ou plus probablement je comprends Kotlin Generics complètement faux. Y a-t-il une façon correcte de le faire?Kotlin « out » et « dans » et génériques - bon usage

/** 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 

et être en mesure de lancer une application avec

val mySet = mutableSetOf<MyWhatever>() 
mySet.load() 

Répondre

5

Votre code contient une distribution non contrôlée as Collection<Nothing>. Créer une distribution non contrôlée est un moyen de dire au compilateur que vous en savez plus sur les types que ce qu'il fait, vous permettant de violer certaines restrictions, y compris celles introduites par la variance des génériques.

Si vous supprimez la distribution et de laisser seulement la partie de celui-ci, à savoir

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

vérifié sans contrôle Le compilateur ne vous permettra pas d'ajouter les éléments dans la ligne this.addAll(loaded). Fondamentalement, la distribution non cochée que vous avez faite est un hack sale, parce que le type Nothing n'a pas de valeurs réelles dans Kotlin, et vous ne devriez pas faire semblant. Cela fonctionne uniquement parce que le MutableSet<out Serializable> en même temps signifie MutableSet<in Nothing> (ce qui signifie que l'argument de type réel est effacé - il peut être n'importe quel sous-type de Serializable - et comme on ne sait pas quel est le type d'élément de l'ensemble, il n'y a rien mis en toute sécurité dans l'ensemble).

Une des façons typées pour mettre en œuvre la deuxième fonction est la suivante:

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>()) 
     } 
    } 
} 

Si vous voulez le faire fonctionner avec des ensembles qui contiennent des éléments plus concrets que Serializable ou Any, vous pouvez faire avec des paramètres de type réifiés. Cela rend la ligne du compilateur le type déclaré/inférées sur les sites d'appel-load, de sorte que le type se propage à filterIsInstance et les articles sont vérifiés correctement:

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>()) 
     } 
    } 
} 

Ou vérifier les éléments d'une autre manière qui vous convient meilleur. Par exemple. loaded.forEach { if (it !is T) throw IllegalArgumentException() } avant la ligne addAll.

+0

La deuxième option a bien fonctionné, et merci d'expliquer "Rien"! –