Els tipus de col·lecció (sovint anomenats estructures de dades) et permeten desar múltiples valors, habitualment del mateix tipus, de manera organitzada.
Introducció
Una col·lecció pot ser una llista ordenada, un agrupament de valors únics o un mapatge de valors d’un tipus de dada a valors d’un altre tipus.
En moltes aplicacions, probablement has vist dades mostrades com una llista: contactes, configuració, resultats de cerca, etc.
Per crear aplicacions que impliquin quantitats arbitràries de dades, cal que aprenguis a fer servir col·leccions.
Array
Un array és la manera més simple d’agrupar un nombre arbitrari de valors en els teus programes.
Un Array
representa més d’un valor.
Concretament, un array és una seqüència de valors que tenen tots el mateix tipus de dada:
Int | Int | Int | Int | Int | Int |
Un array conté múltiples valors anomenats elements, o, de vegades, ítems.
71 | 600 | 33 | 9 | 4 | 14 |
Els elements d’un array estan ordenats i s’hi accedeix amb un índex.
71 | 600 | 33 | 9 | 4 | 14 |
0 | 1 | 2 | 3 | 4 | 5 |
Què és un índex? Un índex és un nombre enter que correspon a un element de l’array. Un índex indica la distància d’un ítem respecte de l’element inicial d’un array. Això s’anomena indexació des de zero. El primer element de l’array és a l’índex 0, el segon element és a l’índex 1, perquè és a un lloc del primer element, i així successivament.
A la memòria del dispositiu, els elements de l’array s’emmagatzemen l’un al costat de l’altre.
Això té dues implicacions importants:
-
Accedir a un element d’un array pel seu índex és ràpid. Pots accedir a qualsevol element aleatori d’un array pel seu índex i esperar que trigui aproximadament el mateix temps que accedir a qualsevol altre element aleatori. Per això es diu que els arrays tenen accés aleatori.
-
Un array té una mida fixa. Això vol dir que no pots afegir elements a un array més enllà d’aquesta mida. Intentar accedir a l’element de l’índex 100 en un array de 100 elements llançarà una excepció perquè l’índex més alt és 99 (recorda que el primer índex és 0, no 1). Pots, però, modificar els valors als índexs de l’array.
Per declarar un array en codi, utilitzes la funció arrayOf()
:
val plants = arrayOf<String>("Carrot", "Daikon", "Onion", "Garlic", "Beet")
La funció arrayOf()
pren els elements de l’array com a paràmetres i retorna un array del tipus que coincideix amb els paràmetres passats.
Això pot semblar una mica diferent d’altres funcions que has vist perquè arrayOf()
té un nombre variable de paràmetres:
- Si passes dos arguments a
arrayOf()
, l’array resultant conté dos elements, amb índexs 0 i 1. - Si passes tres arguments, l’array resultant tindrà 3 elements, amb índexs de l’1 al 2.
Com que Kotlin fa servir inferència de tipus, pots ometre el nom del tipus quan crides arrayOf()
:
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
Pots fer algunes coses interessants amb els arrays.
Per exemple, igual que amb els tipus numèrics Int
o Double
, pots sumar dos arrays fent servir l’operador més (+
).
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
val animals = arrayOf("Cat", "Dog", "Cow", "Chicken", "Zoo")
val life = plants + animals
El resultat és un nou array que conté tots els elements de l’array plants
i els elements de l’array animals
.
Accedeix a un element d’un array
Pots accedir a un element d’un array pel seu índex:
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
require(plants[1] == "Daikon")
require(plants[3] == "Garlic")
Això s’anomena sintaxi de subíndex. Consta de tres parts:
- El nom de l’array.
- Un claudàtor d’obertura (
[
) i un de tancament (]
). - L’índex de l’element de l’array dins dels claudàtors.
També pots establir el valor d’un element d’un array pel seu índex.
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
require(plants[1] == "Daikon")
plants[1] == "Banana"
require(plants[1] == "Banana")
Abans hem comentat que no pots canviar la mida d’un array.
Què passaria si ho intentessis?
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
plants[5] == "Banana"
Es llança una excepció ArrayIndexOutOfBounds
perquè l’array només té 5 elements.
ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
Si vols fer que un array sigui més gran del que és, has de crear un nou array.
fun main() {
val plants = arrayOf("Carrot", "Daikon", "Onion", "Garlic", "Beet")
require(plants.size == 5)
// Has creat un objecte nou !
val morePlants = plants + "Banana"
require(morePlants.size == 6)
plants[0] = "Apple"
morePlants[0] = "Orange"
require(plants[0] == "Apple")
require(morePlants[0] == "Orange")
}
Tot i que els arrays són un dels aspectes fonamentals de la programació, fer-los servir per a tasques que requereixen afegir i eliminar elements, garantir la unicitat d’una col·lecció o mapar objectes a altres objectes no és especialment simple ni directe, i el codi de la teva app es podria convertir ràpidament en un embolic.
Per això, la majoria de llenguatges de programació implementen tipus de col·leccions especials per gestionar situacions que es donen habitualment en apps del món real.
Llista
Una llista és una col·lecció ordenada i redimensionable, normalment implementada com un array redimensionable.
71 | 600 |
Quan l’array s’omple fins a la seva capacitat i proves d’inserir un element nou…
71 | 600 | 33 | 9 | 4 | 14 |
L’array es copia a un array nou més gran:
71 | 600 | 33 | 9 | 4 | 14 | 27 |
Amb una llista, també pots inserir elements nous entre altres elements en un índex específic.
71 | 600 | 33 | 56 | 9 | 4 | 14 | 27 |
Així és com les llistes poden afegir i eliminar elements.
En la majoria de casos, triga el mateix temps afegir qualsevol element a una llista, independentment de quants elements hi hagi a la llista. De tant en tant, si afegir un element nou faria que l’array superés la mida definida, pot ser necessari moure els elements de l’array per fer lloc als elements nous.
Les llistes fan tot això per tu, però, internament, només és un array que se substitueix per un de nou quan cal.
List
i MutableList
Els tipus de col·lecció implementen una o més interfícies.
Com aprendràs a TODO, les interfícies proporcionen un conjunt estàndard de propietats i mètodes que una classe ha d’implementar.
Una classe que implementa la interfície List
proporciona implementacions de totes les propietats i mètodes de la interfície List
. El mateix passa amb MutableList
.
Aleshores, què fan List
i MutableList
?
-
List
és una interfície que defineix propietats i mètodes relacionats amb una col·lecció ordenada d’elements de només lectura. -
MutableList
amplia la interfícieList
definint mètodes per modificar una llista, com afegir i eliminar elements.
Aquestes interfícies només especifiquen les propietats i mètodes d’una List
i/o MutableList
. Correspon a la classe que les implementa determinar com s’implementa cada propietat i mètode.
La implementació basada en arrays descrita més amunt és la que utilitzaràs la major part del temps, si no sempre, però Kotlin permet que altres classes implementin List
i MutableList
.
La funció listOf()
Com arrayOf()
, la funció listOf()
rep els elements com a paràmetres, però retorna una List
en lloc d’un array.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
List
té una propietat size
per obtenir el nombre d’elements de la llista.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
require(animals.size == 4)
Accedeix als elements d’una llista
Com amb un array, pots accedir a un element en un índex específic d’una List
utilitzant la sintaxi de subíndex.
Pots fer el mateix amb el mètode get()
.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
require(animals.get(0) == "Cat")
La sintaxi de subíndex i el mètode get()
prenen un Int
com a paràmetre i retornen l’element d’aquell índex.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
require(animals[2] == "Cow")
Com Array
, List
està indexada des de zero; per exemple, el quart element serà a l’índex 3.
A més d’obtenir un element pel seu índex, també pots cercar l’índex d’un element específic amb el mètode indexOf()
.
El mètode indexOf()
busca a la llista l’element donat (passat com a argument) i retorna l’índex de la primera ocurrència d’aquell element.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
require(animals.indexOf("Chicken") == 3)
Si l’element no és a la llista, retorna -1
.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
require(animals.indexOf("Fox") == -1)
Itera els elements d’una llista amb un bucle for
Una tasca habitual en programació és fer una acció un cop per a cada element d’una llista.
Kotlin inclou una característica anomenada bucle for
per aconseguir-ho amb una sintaxi concisa i llegible.
Sovint ho veuràs referit com a “iterar” per una llista.
val animals = listOf("Cat", "Dog", "Cow", "Chicken")
for (animal in animals) {
println("Animal: $animal")
}
Per recórrer una llista, utilitza la paraula clau for
, seguida d’un parell de parèntesis. Dins dels parèntesis, inclou un nom de variable, seguit de la paraula clau in
, i després el nom de la col·lecció. Després del parèntesi de tancament, ve un parell de claus d’obertura i tancament, on inclous el codi que vols executar per a cada element de la col·lecció. Això és el cos
del bucle. Cada vegada que s’executa aquest codi s’anomena una iteració.
La variable abans de la paraula clau in
no es declara amb val
ni var
; s’assumeix que és de només lectura. La pots anomenar com vulguis.
Si a una llista li poses un nom en plural, com animals
, és comú anomenar la variable amb el singular, com animal
. També és habitual anomenar-la item
o element
.
Aquesta variable s’utilitza com a variable temporal corresponent a l’element actual de la col·lecció — l’element de l’índex 0 a la primera iteració, l’element de l’índex 1 a la segona, i així successivament — i es pot fer servir dins de les claus.
Afegeix elements a una llista
La capacitat d’afegir, eliminar i actualitzar elements d’una col·lecció és exclusiva de les classes que implementen la interfície MutableList
.
Si vols poder afegir elements sovint a una llista, has de cridar específicament la funció mutableListOf()
, en lloc de listOf()
, quan creïs una llista de la qual vulguis afegir i eliminar elements.
Hi ha dues versions de la funció add()
.
La primera add()
té un únic paràmetre del tipus d’element de la llista i l’afegeix al final de la llista.
val animals = mutableListOf("Cat", "Dog", "Cow", "Chicken")
animals.add("Tiger")
require(animals[4] == "Tiger")
L’altra versió d’add()
té dos paràmetres:
- El primer paràmetre correspon a l’índex on s’ha d’inserir el nou element.
- El segon paràmetre és l’element que s’afegeix a la llista.
val animals = mutableListOf("Cat", "Dog", "Cow", "Chicken")
animals.add(1,"Tiger")
require(animals[1] == "Tiger")
require(animals[2] == "Dog")
Actualitza elements en un índex específic
Pots actualitzar elements existents amb la sintaxi de subíndex:
val animals = mutableListOf("Cat", "Dog", "Cow", "Chicken")
animals[1] ="Tiger"
require(animals[1] == "Tiger")
require(animals[2] == "Cow")
Elimina elements d’una llista
Pots eliminar elements d’una llista utilitzant el mètode remove()
.
El mètode remove()
pren un únic paràmetre, que és l’element que s’ha d’eliminar.
val animals = mutableListOf("Cat", "Dog", "Cow", "Chicken")
animals.remove("Dog")
require(animals.indexOf("Dog") == -1)
Conjunt
Un conjunt és una col·lecció que no té un ordre específic i no permet valors duplicats. Com és possible una col·lecció així? El secret és el codi hash.
Un codi hash és un Int
produït pel mètode hashCode()
de qualsevol classe de Kotlin. Es pot considerar com un identificador semi-únic per a un objecte de Kotlin.
Un petit canvi a l’objecte, com afegir un caràcter a una String
, dona com a resultat un valor hash molt diferent.
require("Hello".hashCode() == 69609650)
require("Hello!".hashCode() == -2137068113)
Tot i que és possible que dos objectes tinguin el mateix codi hash (anomenat col·lisió de hash), la funció hashCode()
garanteix un cert grau d’unicitat: la major part del temps, dos valors diferents tenen cadascun un codi hash únic.
01 | 02 | 03 | 04 | 05 | … |
Hello | Gos | ||||
Tortuga |
L’array extern — els números emmarcats en blau — corresponen cadascun a un rang (també conegut com a “cubell”) de possibles codis hash.
Cada llista interna — ombrejada en verd a la dreta — representa els ítems individuals del conjunt.
Com que les col·lisions de hash són relativament poc freqüents, fins i tot quan els índexs potencials són limitats, les llistes internes a cada índex de l’array només tindran un o dos elements cadascuna, tret que s’hi afegeixin desenes o centenars de milers d’elements.
Els conjunts tenen dues propietats importants:
-
Cercar un element específic en un conjunt és ràpid — en comparació amb les llistes — especialment per a col·leccions grans. Mentre que
indexOf()
d’unaList
requereix comprovar cada element des del principi fins que es trobi una coincidència, de mitjana, es triga el mateix a comprovar si un element és en un conjunt, sigui el primer element o el número cent mil. -
Els conjunts tendeixen a utilitzar més memòria que les llistes per a la mateixa quantitat de dades, ja que sovint calen més índexs d’array que dades hi ha al conjunt.
El benefici dels conjunts és garantir la unicitat.
Si estas escrivint un programa per fer el seguiment dels usuaris connectats, un conjunt proporciona una manera simple de comprovar si un usuari ja s’ha connectat. Amb grans quantitats de dades, això sovint és preferible a comprovar si un element existeix en una llista, cosa que requereix iterar per tots els elements.
Com List
i MutableList
, hi ha tant Set
com MutableSet
. MutableSet
implementa Set
, de manera que qualsevol classe que implementi MutableSet
ha d’implementar ambdues.
MutableSet
Farem servir un MutableSet
per demostrar com afegir i eliminar elements.
Com List
, Set
té un mètode add()
.
Els elements dels conjunts no tenen necessàriament un ordre, així que no hi ha índex!
val animals = mutableSetOf("cat", "dog", "bird")
require(!animals.add("cat"))
require(animals.size == 3)
require(animals.add("zebra"))
require(animals.size == 4)
require(!animals.add("zebra"))
require(animals.size == 4)
La funció contains()
pren un únic paràmetre i comprova si l’element especificat és dins del conjunt. Si ho és, retorna true
. En cas contrari, retorna false
.
val animals = mutableSetOf("cat", "dog", "bird")
require(animals.contains("cat"))
require(!animals.contains("fish"))
Alternativament, pots usar l’operador in
per comprovar si un element és en una col·lecció:
val animals = mutableSetOf("cat", "dog", "bird")
require("cat" in animals)
require("fish" !in animals)
La funció remove()
pren un únic paràmetre i elimina l’element especificat del conjunt:
require("cat" in animals)
animals.remove("cat")
require("cat" !in animals)