paint-brush
Read-only collections in Kotlin, leads to better coding.by@elye.project
1,287 reads
1,287 reads

Read-only collections in Kotlin, leads to better coding.

by ElyeOctober 15th, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In Kotlin, if you create a <em>List</em> or <em>Map</em>, it is Read-Only (<a href="https://stackoverflow.com/a/33738910/3286489">similar to Immutable, but slightly different</a>). What this means is, you have no <em>add</em> or <em>put</em> or <em>remove </em>function to add to or remove from the collection once it is constructed.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Read-only collections in Kotlin, leads to better coding.
Elye HackerNoon profile picture

In Kotlin, if you create a List or Map, it is Read-Only (similar to Immutable, but slightly different). What this means is, you have no add or put or remove function to add to or remove from the collection once it is constructed.

Note: if you still like to have a mutable collection, you would need to declare a MutableList or MutableMap. My recommendation is only to do that if you really need a temporary list that is mutable.

How to create Read-Only Collections

In Java, to create a List, it would normally as below

List<String> countries = new ArrayList();
      countries.add("Australia");
      countries.add("Korea");
      countries.add("Malaysia");

But we can’t do the same in Kotlin anymore, as you can’t add to the List after it is created.

So to do as the above, you would use the listOf function provided by CollectionKt class of Kotlin.

val countries = listOf("Australia", "Korea", "Malaysia")

Similarly for Map, you could use mapOf function as below.

val mapCountryCapital = mapOf(
              Pair("Australia", "Canberra"),
              Pair("Malaysia", "Putrajaya"))

Filter a collection

Assuming you have a list as below

class Country(val country: String, val continent: String)

val countries = listOf(Country("Korea", "Asia") , 
                       Country("Malaysia", "Asia"),
                       Country("Romania", "Europe"),
                       Country("Somalia", "Africa"))

And you would only want to filter and keep the Asia countries in a new list. You can’t use add or remove. So you can’t use for loop for the process.

With that you have to search to see how this could be achieved. It is as simple as below.

val asiaCountries = countries.filter{ it.continent == "Asia" }

Extracting elements into another list

Assuming you have a list as below

class Country(val country: String, val capital: String)

val countries = listOf(Country("Australia", "Canberra"), 
                       Country("Malaysia", "Putrajaya"),
                       Country("Romania", "Bucharest"))

And now, you want to create a list of only the Capitals. Again, no point looping through the list to extract element from each item of the list, since you can’t mutate the list using add.

You have to search how that is possible. It is as simple as below.

val capitals = countries.map{ it.capital }

Flatten list within list to a new list

Assuming you have a list as below

class Country(val country: String, val cities: List<String>)

val countries = listOf(
     Country("Australia", listOf("Canberra", "Melbourne")), 
     Country("Malaysia", listOf("Putrajaya", "Penang")))

You now want a flatten list of cities for the provided countries. A quick though as below, but is it incorrect, as this is storing the list of cities in a new list.

val capitals = countries.map{ it.cities } // Wrong answer

So how could you do it? Again, no loop through the list and manipulate it. So you have to search what good way to do it. The good news is, it is as simple as below.

val cities = countries.flatmap{ it.cities }

Convert List to Map

Assuming you have a list as below

class Country(val country: String, val capital: String)

val countries = listOf(Country("Australia", "Canberra"),
                       Country("Brazil", "Brasilia"), 
                       Country("Malaysia", "Putrajaya"))

Searching through it would be difficult. You like to have a map so that could could easily find the capital by giving a country.

Again, the Map by default is Read-Only. So that can’t create a Map and put to it. Because of this challenge, you have to search how to get it done. Fortunately, it is also as simple as below

val mapCountryCaptial = 
        countries.associateBy({ it.country }, { it.capital })

Filtering null item when constructing new list

Assuming you have a code as below

val cities = listOf("Xian", "Medan", "Putrajaya", "Canberra")

val mapCapitalCountry= mapOf(
           Pair("Brasilia", "Brazil"),
           Pair("Canberra", "Australia"),
           Pair("Putrajaya", "Malaysia"))

You want to list of the Countries where it’s capital is stated in your city list. The first thought would be easy, just use map.

// Incorrect
val countriesOfListedCapital = cities.map { mapCapitalCountry[it] } 

However, this will result is a list size of 4, with 2 null values within, since Xian and Medan will generate null from mapCapitalCountry[it].

To improve, you could do first filter away the null, and map it.

// Partially correct. 
val countriesOfListedCapital 
       = cities.filter { mapCapitalCountry[it] != null }
               .map { mapCapitalCountry[it] }

This now get a result of 2 item i.e. Australia and Malaysia. However, if you check in detail, the type for countriesOfListedCapital is List<String**?**>. It is a nullable type, which is not ideal.

Thinking further, perhaps we could force it non-nullable by using Elvis Operator, returning empty String instead of null; even though you know this will not happen, since all null has been filtered away.

// Almost correct
val countriesOfListedCapital
      = cities.filter { mapCapitalCountry[it] != null }
              .map { mapCapitalCountry[it] ?: "" }

This is now good. countriesOfListedCapital is nowList<String>. However, this is still not ideal, as you have to explicitly add the Elvis Operator, that you know does nothing.

Well, the good news is, there’s also a collection function that could achieve this, without any complication as above.

val countriesOfListedCapital
      = cities.mapNotNull {  mapCapitalCountry[it]  }

Well, due to the Kotlin List and Map Read-Only collection restriction, it forces me to search the right way to do it, making the code so much concise.

The Kotlin collection has provided a bunch of very useful operations. Listed above are just very few of them. Many more to explore. Have fun check out!

If you like my post and want to get future update, do click 👏👏👏, follow me on medium, Twitter or Facebook