Home > Software engineering >  how to elegantly create a map with only non-null values in Kotlin
how to elegantly create a map with only non-null values in Kotlin

Time:10-07

How can I rewrite this code without having to resort to a MutableMap and conversion to immutable map?

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val map = mutableMapOf("key1" to mandatoryValue)
    optionalValue?.let { map.put("key2", it) }
    return map.toMap()
}

My alternative solution isn't very nice either because it needs an unsafe cast:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf(
        "key1" to mandatoryValue,
        "key2" to optionalValue
    ).filterValues { it != null } as Map<String, String>

What I am looking for is something in the lines of:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf(
        "key1" to mandatoryValue,
        optionalValue?.let { "key2" to it }
    )

CodePudding user response:

toMap() does not necessarily create an immutable map. It is only guaranteed to be read-only. The underlying class instance might be a MutableMap (which in the current implementation is true if it has more than one key). Therefore, toMap() in your first block of code is unnecessary. The MutableMap is automatically upcast to Map when you return it since you specified Map as the return type. So, you could have put

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val map = mutableMapOf("key1" to mandatoryValue)
    optionalValue?.let { map.put("key2", it) }
    return map
}

or

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mutableMapOf("key1" to mandatoryValue).apply {
        if (optionalValue != null) put("key2", optionalValue)
    }

To get the syntax you requested in your last example, you could create an overload of mapOf that accepts and filters null values:

fun <K, V> mapOf(vararg pairs: Pair<K, V>?): Map<K, V> =
    mapOf(*pairs.filterNotNull().toTypedArray())

CodePudding user response:

You can have your own extension function as follows and then use it to filter null values from a Map:

fun <K, V> Map<K, V?>.filterValuesNotNull() = 
    mapNotNull { (k, v) -> v?.let { k to v } }.toMap()

CodePudding user response:

There's nothing wrong with using MutableMap - in fact your first solution (without the redundant toMap()) is already pretty elegant. It's simpler and clearer than any immutable answer will be. Immutable operations come at the cost of additional object creations and copies, so unless you need the guarantees of immutability, it's best to use a MutableMap but only expose it via the Map interface, as you are already doing.

If you really wanted to do it immutably, you could do it like this:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf("key1" to mandatoryValue)  
        (optionalValue?.let { mapOf("key2" to it) } ?: emptyMap())

Or equivalently if you prefer:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf("key1" to mandatoryValue)  
        if (optionalValue != null) mapOf("key2" to optionalValue) else emptyMap()

Or:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val mandatoryMap = mapOf("key1" to mandatoryValue)
    return optionalValue?.let { mandatoryMap   ("key2" to optionalValue) } ?: mandatoryMap
}

Or:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val mandatoryMap = mapOf("key1" to mandatoryValue)
    return if (optionalValue != null) mandatoryMap   ("key2" to optionalValue) else mandatoryMap
}
  • Related