Kolekcje w Kotline, czyli małe rzeczy robią dużą różnicę
Jak przystało na nowocześnie zaprojektowany język programowania, Kotlin posiada kolekcje niezmienne i zmienne „na równych prawach”. W przeciwieństwie do Javy, gdzie kolekcje mutowane są preferowane na poziomie API (by zrobić kolekcję niemutowaną, trzeba się naklepać). BTW, wynika to prostego faktu – przed Javą 1.5 nie było metod o zmiennej liczbie argumentów, więc nie można było zaprojektować jakiegoś sensownego API.
Listing 1. Przykład tworzenie kolekcji
fun main(args: Array<String>) {
val immute = listOf("Ania", "Ela", "Wojtek")
val mutant = mutableListOf("Ania", "Ela", "Wojtek")
mutant.add("Henio")
println(immute)
println(mutant)
}
Na tym poziomie nie ma tu nic zaskakującego. Ogólna konwencja w API jest taka, że mamy pary metod xOf i mutableXOf. Co ważne można w ten sposób tworzyć też mapy:
Listing 2. Tworzenie map
fun main(args: Array<String>) {
val pierscienie = mapOf(9 to "ludzie", 7 to "Krasnoludów", 3 to "Elfy", 1 to "Sauron")
println(pierscienie)
}
W sumie nie ma tu nic niezwykłego. Jest jednak jedna drobna rzecz, która powoduje, że Kotlin wyróżnia się spośród innych języków. Twórcy założyli, że kolekcje niezmienne będą kowariantne. To znaczy, jeżeli napiszemy:
Listing 3. Kowariancja kolekcji niezmiennych
fun main(args: Array<String>) {
val rectangles = listOf(Rectangle())
val shapses: List<Shape> = rectangles
}
To będzie to prawidłowy kod z punktu widzenia systemu typów. Inaczej mówiąc, kowariancja oznacza, iż lista kwadratów jest podtypem listy kształtów, ponieważ kwadrat jest podtypem kształtu. Tak samo zapisany kod dla list kształtów, która jest mutująca:
Listing 4. A tu się wywali
fun main(args: Array<String>) {
val rectangles = listOf(Rectangle())
val shapses: MutableList<Shape> = rectangles
}
Zwróci błąd nieprawidłowego typu. Mała rzecz, a cieszy. Wytłumaczenie tego zachowania jest oczywiste. Jeżeli lista kształtów może zmieniać zawartość, a „pod spodem“ jest lista kwadratów, to dodanie na przykład trójkąta spowoduje błąd. Tyle na dziś.