2016-06-20 19 views
20

Stavo scrivendo un piccolo pezzo di codice in cui gestisco internamente i miei dati in una mappa mutabile, che a sua volta ha elenchi modificabili.Come trasformare una raccolta mutevole in una immutabile

Ho voluto esporre i miei dati all'utente API, ma per evitare pubblicazioni non sicure dei miei dati ho voluto esporlo in collezioni immutabili anche se internamente gestite da quelle mutevoli.

class School { 

    val roster: MutableMap<Int, MutableList<String>> = mutableMapOf<Int, MutableList<String>>() 

    fun add(name: String, grade: Int): Unit { 
     val students = roster.getOrPut(grade) { mutableListOf() } 
     if (!students.contains(name)) { 
      students.add(name) 
     } 
    } 

    fun sort(): Map<Int, List<String>> { 
     return db().mapValues { entry -> entry.value.sorted() } 
       .toSortedMap() 
    } 

    fun grade(grade: Int) = db().getOrElse(grade, { listOf() }) 
    fun db(): Map<Int, List<String>> = roster //Uh oh! 
} 

sono riuscito a esporre solo Map e List (che sono immutabili) nella API pubblica della mia classe, ma le istanze realtà sto esponendo sono ancora intrinsecamente mutevole.

Ciò significa che un utente API può semplicemente trasmettere la mia mappa restituita come ImmutableMap e ottenere l'accesso ai preziosi dati privati ​​interni alla mia classe, che doveva essere protetta da questo tipo di accesso.

Non sono riuscito a trovare un costruttore di copia nei metodi factory collection mutableMapOf() o mutableListOf() e quindi mi chiedevo quale sia il modo migliore e più efficiente per trasformare una raccolta mutabile in una immutabile.

Qualche consiglio o raccomandazione?

risposta

9

Attualmente in Kotlin stdlib non ci sono implementazioni di List<T> (Map<K,V>) che non sarebbe anche implementareMutableList<T> (MutableMap<K,V>). Tuttavia a causa Kotlin's delegation feature implementazioni diventano uno liners:

class ImmutableList<T>(private val inner:List<T>) : List<T> by inner 
class ImmutableMap<K, V>(private val inner: Map<K, V>) : Map<K, V> by inner 

È possibile inoltre la creazione delle controparti immutabili con metodi di estensione:

fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> { 
    if (this is ImmutableMap<K, V>) { 
     return this 
    } else { 
     return ImmutableMap(this) 
    } 
} 

fun <T> List<T>.toImmutableList(): List<T> { 
    if (this is ImmutableList<T>) { 
     return this 
    } else { 
     return ImmutableList(this) 
    } 
} 

Quanto sopra impedisce un chiamante di modificare il List (Map) lanciando a una classe diversa.Tuttavia ci sono ancora motivi per creare una copia del contenitore originale per evitare problemi di sottili come ConcurrentModificationException:

class ImmutableList<T> private constructor(private val inner: List<T>) : List<T> by inner { 
    companion object { 
     fun <T> create(inner: List<T>) = if (inner is ImmutableList<T>) { 
       inner 
      } else { 
       ImmutableList(inner.toList()) 
      } 
    } 
} 

class ImmutableMap<K, V> private constructor(private val inner: Map<K, V>) : Map<K, V> by inner { 
    companion object { 
     fun <K, V> create(inner: Map<K, V>) = if (inner is ImmutableMap<K, V>) { 
      inner 
     } else { 
      ImmutableMap(hashMapOf(*inner.toList().toTypedArray())) 
     } 
    } 
} 

fun <K, V> Map<K, V>.toImmutableMap(): Map<K, V> = ImmutableMap.create(this) 
fun <T> List<T>.toImmutableList(): List<T> = ImmutableList.create(this) 

Mentre quanto sopra non è difficile da attuare ci sono già le implementazioni di liste immutabili e le mappe sia in Guava e Eclipse-Collections.

+1

Una versione completa di questo è stata aggiunta a Klutter bloccando tutte le azioni correlate come un 'Iterator' ricevuto da una lista, una' sottoclasse' da una lista, un 'entrySet' da una mappa e altro. Vedi: http://stackoverflow.com/a/38002121/3679676 –

5

Come accennato here e here, avresti bisogno di scrivere il proprio List implementazione per questo, o utilizzare uno esistente (ImmutableList di Guava viene in mente, o come Eclipse CollectionsAndrew suggerito).

Kotlin applica la muting list (im) solo tramite l'interfaccia. Non esistono implementazioni List che non implementino anche MutableList.

Anche il idiomatica listOf(1,2,3) finisce per chiamare di Kotlin ArraysUtilJVM.asList() che chiama di Arrays.asList() Java che restituisce una pianura vecchio Java ArrayList.

Se si cura più di proteggere la vostra lista interna, di circa l'immutabilità in sé, si può ovviamente copiare l'intera collezione e restituirlo come List, proprio come Kotlin fa:

return ArrayList(original) 
+2

piccola correzione, 'Arrays.asList' restituisce un' java.util. Arrays.ArrayList' (da non confondere con 'java.util.ArrayList'). Questo elenco è in qualche modo immutabile in quanto non è possibile aggiungere o rimuovere elementi, ma è possibile sostituire gli articoli per indice. –

3

lo so questa è una domanda specifica di Kotlin e @Malt è corretta ma vorrei aggiungere un'alternativa. In particolare, trovo lo Eclipse Collections, formalmente GS-Collections, come una migliore alternativa a Guava per la maggior parte dei casi e integra bene le collezioni di Kotlin.

2

Una soluzione classica è quella di copiare i dati, in modo che, anche se modificato, il cambiamento non inciderebbe sulla proprietà privata della classe:

class School { 
    private val roster = mutableMapOf<Int, MutableList<String>>() 

    fun db(): Map<Int, List<String>> = roster.mapValuestTo {it.value.toList} 
} 
Problemi correlati