Understanding Maps 🗺 in the Kotlin.

androidbites Dec 12, 2020

Curious how Kotlin resolve the Java platform’s map?

This Post is given `Silver award` in Reddit at r/android_devs,thanks community for supporting my blogs and it really motivating me! 🙇‍♂️
AndroidBites | Java ☕️ Maps 🗺 on the Kotlin. from android_devs
Silver Awarded on r/AndroidDevs

Originally Posted At :

Java ☕️ Maps 🗺 on the Kotlin.
Most of the Kotlin developers are migrated from the Java environment, they are pretty well versed with uses-cases and application of Collection Framework in the Java platform, but applying the same…

Most of the Kotlin developers are migrated from the Java environment, they are pretty well versed with uses-cases and application of Collection Framework in the Java platform, but applying the same knowledge in a Kotlin codebase isn’t guaranteed to work always. You could end up with a spaghetti codebase base having a long trailing function and making hacks to achieve a functionality that could easily be solved using Kotlin’s standard functions easily. Kotlin stdlib which offers generic interfaces, classes, and functions for creating, populating, and managing Collections of any type.

In Today’s article, we will try to understand how Kotlin catered to Java Maps in his its colors. Disclaimer This article is specific to Kotlin JVM as collection implementation varies with the platform you are targeting in Kotlin. I’m Chetan Gupta, I do tech stuff, don’t forget to checkout from other blogs as well from AndroidBites, without any delays, Let’s get started.



Map 🗺

It’s a collection of key-value pairs, mainly focus on retrieving a value against the key.

// signature
interface Map<K, out V>
// Parameters
// K - the type of key
// V - the type of map values

  • Keys are unique and similar to the index of an array.
  • A map can hold only one value against a key.
  • The map is a read-only collection but for the sake of understanding, I will be addressing it as Immutable*.
  • The modifying part is supported using Mutable maps, discussed later in the article.
  • Map<K, V> is not an inheritor of the Collection Interface, but it’s the part of the Kotlin Collection type.

Entries 🚪

An entry represents a key/value pair in a map.

// signature
interface Entry<out K, out V>
// Parameters
// K - the type of key
// V - the type of map values

A map contains a Set<Entry<K,V>> type entries to store its key/value, since its a Set, the uniqueness is enforced into the map entries.

val map = object : Map<String, Int>{
    override val entries: Set<Entry<String, Int>>
        get() = //stuff...
}

Even though an entry is representing a key-value pair in a map, there is no way to directly create a map using it i.e if you can see possible ways we can create a map :

fun <K, V> mapOf(): Map<K, V> 
fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> 
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

There is no function which creates a map using the Entry, if you check Entry Interface from docs, you will see

fun <K, V> Entry<K, V>.toPair(): Pair<K, V>

Entry can be converted to Pair but not to a map directly (like a SingletonMap), even if you focus on entries in the map which are Set<Entry<K, V>> there is no function that converts a Set<Entry<String, Int>> into Map.

fun <K, V> Iterable<Entry<K, V>>.toMap(): Map<K, V> Or any other isn't supported
// but instead there is
fun <K, V> Iterable<Pair<K, V>>.toMap(): Map<K, V>

Since there is no direct support of entries to map, hence we can conclude that it’s used only as an internal implementation of key/value entries, but for the outside world it has to implement using Pair type.

// enteries to map example
val entries:Set<Entry<String, Int>> = setOf(entry)
val map = entries.map { entry -> entry.toPair() }.toMap()
// here, entries.map { entry -> entry.toPair() } 
// is List<Pair<String,Int>> i.e Iterable<Pair<K, V>>.toMap()

Since we are creating Maps using Pair , let’s focus on what is it?



Pair 👥

A Pair represents a generic pair of two values. It is a separate data structure, which is not a part of the Collection Framework. It can be used for any purpose.

data class Pair<out A, out B> : Serializable

It isn’t necessary that the first and the second the element of the Pair holds any relation with each other, but Entry’s first element i.e key has a relation with the second element i.e value.

But since Entry can’t be used to create maps directly there was no logical point in having a standard extension that converts Pair into Map.Entry type, thus stdlib doesn’t have Pair.toEntry() function.

There is a discussion in the form regarding it but it goes nowhere.

Pair should implement Map.Entry
// Here’s a pair: val cToC: Pair<String, Char> = “C” to ‘c’ // Maps are constructed with pairs: val m: Map<String, Char> = mapOf(“A” to ‘a’, “B” to ‘b’, cToC) // Inputs are pairs, but outputs are Map.Entry. Why the difference? val entrySet: Set<Map.Entry<String…

Since Pair are used to create Map, the extension below supports that Idea.

// Pair Iterable has a extension which convert 
// List<Pair<String,Int>> to Map
fun <K, V> Iterable<Pair<K, V>>.toMap(): Map<K, V>
// other implementation with d

Map is the base Interface for AbstractMap and MutableMap, so all the core ADT functions such as get , put , contains , containsKey , isEmpty , size etc are implemented in it.

Check out the complete list of base functions from docs :

Map - Kotlin Programming Language

mapOf 👷‍♂️

If you have focused on Map signature interface Map<K, out V>, it’s an interface, i.e it needs to have an implementing class.

In Kotlin, we have three functions for implementing maps and all of them are resolved using different implementations of Map.

// Kotlin Standard Library function
fun <K, V> mapOf(): Map<K, V> 
fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> 
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

fun <K, V> mapOf(): Map<K, V> = emptyMap()
// emptyMap is resolved by
object EmptyMap : Map<Any?, Nothing>, Serializable
// which return read-only empty map implementation

fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> 
  = java.util.Collections.singletonMap(pair.first, pair.second)
// In java.util.Collections, 
public static Map singletonMap(K key, V value) 
//a function that return an immutable map, which is serializable.
//it is mapping only the specified key to the specified value.

fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> 
  = if (pairs.size > 0){
      pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) 
    }else{ 
      emptyMap()
    }
// Linked HashMap signature
class LinkedHashMap<K, V> : MutableMap<K, V> 
// since MutableMap<K, V> has Map as parent class
// Casting it to map make it immutable* (read-only)

So if you are working with a map which has more than one entry, is highly likely to be resolved by using an implementation of LinkedHashMap. We will know about it later in the article.


TLDR ☕️

Maps :

  • Maps are read-only key/value pair of collection
  • They use Entry to represent the internal state of key/value
  • You use Pairs to create a map
  • Maps implementation varies but is mostly resolved with LinkedHashMap


Mutable Map 📝

As the name suggests, it’s a mutable implementation of a Map. It’s a modifiable version of the map.

interface MutableMap<K, V> : Map<K, V>

Similar to Map, MutableMap has MutableEntry that represents a key/value pair but unlike Entry it is mutable :

abstract fun setValue(newValue: V): V

Entries in the mutableMap is now a MutableSet :

abstract val entries: MutableSet<MutableEntry<K, V>> is a MutableSet

Since Map was immutable*(read-only), there was no mutable function such as :

// return old value, if updating existing entry else null
abstract fun put(key: K, value: V): V?
abstract fun putAll(from: Map<out K, V>)
abstract fun clear()
// return removed value, if not existing then null
abstract fun remove(key: K): V?
open fun remove(key: K, value: V): Boolean

All the functions available in the Map are overridden using mutable type variants, You can check them out from docs :

MutableMap - Kotlin Programming Language

Mutable Pair? 🤔

Since you now see everything is implemented in Mutable Maps using mutable types, means Pair which are used to construct mutable map should have a mutable implementation as well, isn’t it?

Logically your thinking is right but only if you look it in terms of Collection, Pairs are not part of collection.

The immutability and Mutability collections concept is introduced to provide a read-only protection*. This helps Kotlin Compiler to show an error at compile-time rather than runtime, for example In Java, java.util.Collections.unmodifiableList(java.util.List) the error would be only visible to the user when he tries to update the unmodifiable list at runtime.

read-only protections, are used to safeguard values in a collection from accidental updates

So how do we update Pair values?

It’s easy, you can directly use the copy constructor, for example :

val pair = "2" to 1
val updatedPair = pair.copy(first = "1")

The last thing in your mind would be, do we need to use ListOf<Pair<K,V>> or MutableListOf<Pair<K,V>> to create a mutable map?

To answer this, you need to know both List and MutableList both are of an iterable type so doesn’t matter, you can create a map using both.

// Pair Iterable has a extension which convert 
// List<Pair<String,Int>> to Map
fun <K, V> Iterable<Pair<K, V>>.toMap(): Map<K, V>
// other implementation with d

Other part is you can’t directly convert a List or MutableList to MutableMap, there is no direct extension available :

// you need to first convert to map 
mutableListOf<Pair<String,Int>>(pair,updatedPair).toMap()
// then you have access to mutableMap extension
mutableListOf<Pair<String,Int>>(pair,updatedPair).toMap().toMutableMap()
//signature
fun <K, V> Map<out K, V>.toMutableMap(): MutableMap<K, V>

MutableMap is the base interface of AbstractMutableMap , HashMaps and LinkedHashMap.



mutableMapOf 👷‍♂️

Similar to Map signature, interface MutableMap<K, out V>, it’s an interface, i.e it needs to have an implementing class.

In Kotlin, we have two function implementation of these and both of them is resolved using LinkedHashMap

// Kotlin Standard Library function
fun <K, V> mutableMapOf(): MutableMap<K, V>
fun <K, V> mutableMapOf(vararg pairs: Pair<K, V> ): MutableMap<K, V>

fun <K, V> mutableMapOf(): MutableMap<K, V> = LinkedHashMap()
// mutableMapOf is resolved using LinkedHashMap
// as discussed earlier Linked HashMap signature
class LinkedHashMap<K, V> : MutableMap<K, V>

fun <K, V> mutableMapOf(vararg pairs: Pair<K, V>): MutableMap<K, V> 
  = LinkedHashMap<K, V>(mapCapacity(pairs.size))
    .apply { putAll(pairs) }
// resolved with LinkedHashMap only


TLDR ☕️

Mutable Maps :

  • Mutable Maps are modifiable key/value pairs of collection therefore contain functions such as put, remove, clear etc that can modify the map.
  • They use MutableEntry to represent the internal state of key/value
  • Pairs don’t have mutable counter-part, Pairs are used to create the map, then that map is transformed to MutableMap.
  • All of the function implementations is resolved by LinkedHashMap

HashMap 📝

In Kotlin JVM, HashMap is a type alias for Java’s HashMap

// Kotlin signature
typealias HashMap<K, V> = HashMap<K, V>
// Java signature
class HashMap<K,V> 
  extends AbstractMap<K,V> 
  implements Map<K,V>, Cloneable, Serializable
// AbstractMap<K,V> is a skeletal implementation of the Map interface,
// Java's Map<K,V> is very close to MutableMap<K,V> than kotlin's Map<K,V>
// i.e it contains mutable opertaions internally, though use structures named 
// similar to Kotlin's Map<K,V> like Map.Entry but are infact Mutable Entry
Java HashMap has three constructors
 - HashMap() 
 - HashMap(int initialCapacity)
 - HashMap(int initialCapacity,float fillRatio)
 - HashMap(Map<K,V> fromMap)

HashMap in Java is a Hash table based implementation of the (Mutable) Map interface.

  • Insertion Order is not preserved, insertion is based on the hashcode of keys, not the values
  • Duplicate keys entry is not allowed
  • Heterogeneous key/value entry is allowed
  • Null keys are allowed but only once, you can store any null values

NOTE.

Even if Kotlin uses typealias and call Java Hashmap the interpolation with Kotlin maps its return types to the MutableMap interface of Kotlin, i.e when you call hashMap.entries you will get MutableSet<MutableEntry> not Java’s Set<Entry>.

Learn more about all the APIs of HashMap from:

HashMap (Java Platform SE 8 )
This implementation provides constant-time performance for the basic operations ( get and put), assuming the hash…


hashMapOf 👷‍♂️

Kotlin provides two implementations to create HashMap

// Kotlin Standard Library function
fun <K, V> hashMapOf(): HashMap<K, V>
fun <K, V> hashMapOf(vararg pairs: Pair<K, V>): HashMap<K, V>

fun <K, V> hashMapOf(): HashMap<K, V> = HashMap<K, V>()
// hashmapOf directly create HashMap object default constructor
// Java HashMap default constructor create HashMap with 
// * inital capacity 16 and * LoadFactor 0.75

fun <K, V> hashMapOf(vararg pairs: Pair<K, V>): HashMap<K, V> 
      = HashMap<K, V>(mapCapacity(pairs.size)).apply { putAll(pairs) }
// this factory function takes pair and uses HashMap's second constructor which
// start off with the object capacity as the number of pairs passed

Another way by which you can create a hashmap is by creating an object directly using any of the available java constructors.

val hashMap = HashMap<String,Int>()

Convert Map to HashMap?

There is no direct extension to convert map to hashmap as it was available in mutable map, so you can :

// Directly use the 4th constructor example
 val contact = mapOf("chetan" to "dev.ch8n@gmail.com")
 val hashMapOfContact = HashMap(contact)
//Or create and add all entries
 val hashMapOfContact = HashMap<String,Int>()
 hashMapOfContact.putAll(contact)


TLDR ☕️

Hash Maps:

  • They are a direct port of java.util.HashMap, whose underlying implementation is based on HashTable.
  • Insertion Order is not preserved, because it’s sorted according to hashcode of the keys
  • No special Kotlin standard extension is available over them, you can directly create them using constructor or use standard static factory functions of Kotlin.
  • Mutable collection in nature.


LinkedHashMap ⛓📝

In Kotlin JVM, similar to HashMap, LinkedHashMap is a type alias for Java’s LinkedHashMap

// Kotlin signature
typealias LinkedHashMap<K, V> = LinkedHashMap<K, V>
// Java signature
class LinkedHashMap<K,V>
  extends HashMap<K,V>
  implements Map<K,V>
// Available Constructors
Java LinkedHashMap has three constructors
 - LinkedHashMap() 
 - LinkedHashMap(int initialCapacity)
 - LinkedHashMap(int initialCapacity,float fillRatio)
 - LinkedHashMap(Map<K,V> fromMap)

LinkedHashMap in Java is a Hash table and LinkedList based implementation of the (Mutable) Map interface.

  • Doubly LinkedList is used to preserve the order of insertion.
  • Features are similar to HashMap including methods and constructors.
  • Useful to create a Cache object that can expire data using some criteria that you define or LRUs ( least recently used ) by overriding the removeEldestEntry()

Check out my LRU implementation in Repository.

AndroidBites | 5 Steps to LRU Cache in Repository.
cache in repository android | cache data in ViewModel | restore data in android | save network call | cache network response | LRU cache in android | cache in android | Easy android cache | save data in repository | best practice Repository pattern | save data android | cache using LinkedHashmap

linkedMapOf ️️👷‍♂️

Kotlin provides two implementations to create LinkedHashMap

// Kotlin Standard Library function
fun <K, V> linkedMapOf(): LinkedHashMap<K, V>
fun <K, V> linkedMapOf(vararg pairs: Pair<K, V>): LinkedHashMap<K, V>

fun <K, V> linkedMapOf(): LinkedHashMap<K, V> = LinkedHashMap<K, V>()
// linkedMapOf directly create LinkedHashMap object default constructor
// Java LinkedHashMap default constructor create LinkedHashMap with 
// * inital capacity 16 and * LoadFactor 0.75

fun <K, V> linkedMapOf(vararg pairs: Pair<K, V>): LinkedHashMap<K, V> 
          = pairs.toMap(LinkedHashMap(mapCapacity(pairs.size)))
    // this factory function takes pair and uses LinkedHashMap's second constructor which
    // start off with the object capacity as the number of pairs passed

Other way by which you can create a Linkedhashmap is by creating an object directly using any of the available java constructors.

val linkedHashMap = LinkedHashMap<String,Int>()

Convert Map to LinkedHashMap?

There is no direct extension to convert map to hashmap as it was available in mutableMap, so you can :

// Directly use the 4th constructor example
 val contact = mapOf("chetan" to "chetan.garg36@gmail.com")
 val linkedMapOfContact = LinkedHashMap(contact)
// Or create and add all entries
 val linkedMapOfContact = LinkedHashMap<String,Int>()
 linkedMapOfContact.putAll(contact)



TLDR ☕️

LinkedHashMaps:

  • They are a direct port of java.util.LinkedHashMap<K,V>, whose underlying implementation is based on HashTable and LinkedList.
  • Doubly LinkedList is used to preserve the order of insertion
  • No special Kotlin standard extension are available over them, you can directly create them using constructors or use standard static factory functions of Kotlin.
  • Mutable collection in nature.

Bonus Points: 🤩

Since you made it this far in the post, here is your reward!

Some points that you need to know about maps :

HashMap/LinkedHashMap

  • They are not thread-safe, doesn’t have synchronized functions
  • The null key is allowed only once
  • Performs faster in the multithreaded environment due to lack of synchronization
  • They are not a legacy DS, HashMap introduced in 1.2, LinkedHashMap introduced in 1.4

HashTable

  • They are thread-safe
  • The null key isn’t allowed
  • Perform slow in the multithreaded environment due to synchronization
  • It is a legacy DS, introduced in 1.0

Conclusion 💆🏻‍♀️

Hopefully, you understand how Kotlin handles Collection over the JVM platform and some of the key points such as:

  • Entries that can’t be directly converted to Map,
  • Pair doesn’t have Mutable type,
  • It's not always true that your map is a linkedHashMap
  • Kotlin JVM resolves Hashmap and LinkedHashMap using Java APIs. etc

Hope you find the post informative, if like more such content you can check more of my blogs.

Until next time, Happy Hacking! 👨🏻‍💻.


Enjoy the article?

a clap is much appreciated if you enjoyed. No sign up or cost associated :)




Chetan gupta

Hi there! call me Ch8n, I'm a mobile technology enthusiast! love Android #kotlinAlltheWay, CodingMantra: #cleanCoder #TDD #SOLID #designpatterns

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.