how to elegantly create a map with only non-null values in Kotlin - kotlin

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 }
)

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()

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())

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
}

In kotlin 1.6
buildMap {
put("key1", mandatoryValue)
if (optionalValue != null)
put("key2", optionalValue)
}
This creates a mutableMap under the sheets, but is quite elegant and readable <3

Related

Kotlin - Override a function with Generic return type

I have an interface. I want to make the return type of one of the function in that interface as generic type. Depend on how it is going to override, the return type will determine. When I try below code, I get errors Conflicting overloads: . I am new to Kotlin.
interface Loader<T> {
fun <T> doSomething(inputParams: Map<String, String> = emptyMap()): T?
fun cancelSomething(inputParams: Map<String, String> = emptyMap())
}
class LoaderHandler<MutableMap<String, String>> (private val foo: Foo) : Loader<MutableMap<String, String>> {
override fun doSomething(inputParams: Map<String, String>): MutableMap<String, String>? {
// some code goes here.
return mapOf("x" to "y")
}
override fun cancelSomething (inputParams: Map<String, String>) {
println("cancelSomething")
}
How can I implement the doSomething(...) function with return type of Map.
Delete <T> in
fun <T> doSomething(inputParams: Map<String, String> = emptyMap()): T?
It is not doing what you think it is.
Additionally,
class LoaderHandler<MutableMap<String, String>> (private val foo: Foo) : Loader<MutableMap<String, String>> {
should be
class LoaderHandler(private val foo: Foo) : Loader<MutableMap<String, String>> {

Kotlin: filterNotNull for values in a Map<K,V?>

Here's the Kotlin code I'd like to write (with extra type annotations for clarity):
fun test(alpha: String, beta: String, gamma: String? = null, delta: String? = null) {
val r1: Map<String, String?> =
hashMapOf(
"alpha" to alpha,
"beta" to beta,
"gamma" to gamma,
"delta" to delta
)
val r2: Map<String, String> = r1.filterValues { it != null }
callSomeFunction(r2) // expects a parameter of type Map<String, String>
}
Unfortunately, r1.filterValues { it != null } gives me back a Map<String, String?>, not a Map<String, String>. I understand why this is; it's the same reason that listOf(1, null).filter { it != null } has a different type from listOf(1, null).filterNotNull(). However, I still need to solve my problem!
Is there an idiomatic way to "filterValuesNotNull" from a Map?
Or, stepping up a level, is there some other idiomatic way to say "give me a Map of the following arguments, but skip those whose values are null"? I could resort to this, but I don't want to:
fun test(alpha: String, beta: String, gamma: String? = null, delta: String? = null) {
var r1: MutableMap<String, String> = mutableMapOf(
"alpha" to alpha,
"beta" to beta
)
if (gamma != null) {
r1["gamma"] = gamma
}
if (delta != null) {
r1["delta"] = delta
}
callSomeFunction(r1) // expects a parameter of type Map<String, String>
}
The functions that in my opinion should be in the stdlib:
fun <K,V: Any> Map<K,V?>.filterNotNullValuesTo(destination: MutableMap<K,V>): Map<K, V> {
for ((key, value) in entries) if (value != null) destination[key] = value
return destination
}
fun <K,V: Any> Map<K,V?>.filterNotNullValues(): Map<K,V> = filterNotNullValuesTo(mutableMapOf())
Here's 2 ways to do it, although whether either is idiomatic, I do not know.
#Suppress("UNCHECKED_CAST)
fun filterNotNullUnchecked(map: Map<String, String?>): Map<String, String> =
map.filterValues { it != null } as Map<String, String>
EDIT: As #Tenfour04 pointed out, this isn't unsafe, just unchecked because of type erasure, and adding a Suppress("UNCHECKED_CAST) annotation makes the warning go away.
fun filterNotNullUgly(map: Map<String, String?>): Map<String, String> {
val map2 = HashMap<String, String>()
for ((k, v) in map) if (v != null) map2[k] = v
return map2
}
hm, I suppose, it can be decided on that way
val newMap: Map<String, String> = r1.mapNotNull { (key, value) ->
value?.let { key to it }
}.toMap()

What's the equivalent of `mapNotNull` that result in map?

I can convert a List<Int?> to List<Int> using mapNotNull function as shown below.
#Test
fun main() {
val testData = listOf(1, null, 3, null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: List<Int?>): List<Int> {
return testData.mapNotNull{ it }
}
How could I convert Map<String, Int?> to Map<String, Int>?
The below with testData.filter { it.value != null } doesn't works, as it still produce Map<String, Int?>.
#Test
fun main() {
val testData = mapOf("One" to 1, "Two" to null, "Three" to 3, "Four" to null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: Map<String, Int?>): Map<String, Int> {
return testData.filter { it.value != null }
}
Well, not really out of the box (in the sense that you get Map<String, Int> immediately), but what about filterValues?
testData.filterValues { it != null } // gives Map<String, Int?> but without null-values
Combining or replacing that with mapValues (maybe you can use a default value instead of null?):
// combining:
testData.filterValues { it != null }
.mapValues { (_, value) -> value as Int }
// replacing:
testData.mapValues { (_, value) -> value ?: /* default value */ 0 }
Both give a Map<String, Int> but the first creates and fills 2 maps under the hood and the second uses 0 instead of null.
You can also simplify the filterValues-variant with an appropriate unchecked cast, as "we know it better":
testData.filterValues { it != null } as Map<String, Int> // unchecked cast, because: we really do know better, do we? ;-)
Alternatively, you could also just handle all entries the way you knew already (using mapNotNull) and then create a new map out of it:
testData.asSequence()
.mapNotNull { (key, value) ->
value?.let {
key to it
}
}
.toMap() // giving Map<String, Int>
If you require that more often you may even want to have your own extension function in place:
#Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
Now you can use it similar as to follows:
testData.filterValuesNotNull() // giving Map<String, Int>
Possible alternative with custom helper function:
inline fun <K, V, R> Map<K, V>.mapValuesNotNullToMap(transformValue: (V) -> R?): Map<K, R> =
buildMap {
this#mapValuesNotNullToMap.entries.forEach { (key, value) ->
transformValue(value)?.let { put(key, it) }
}
}

Kotlin type auto boxing vs primitive

class Remember private constructor() {
private var data: ConcurrentMap<String, Any> = ConcurrentHashMap()
private fun <T> saveValue(key: String, value: T): Remember {
data[key] = value
return this
}
private fun <T> getValue(key: String, clazz: Class<T>): T? {
val value = data[key]
var castedObject: T? = null
//Failed here
if (clazz.isInstance(value)) {
castedObject = clazz.cast(value)
}
return castedObject
}
fun putInt(key: String, value: Int): Remember {
return saveValue(key, value)
}
fun getInt(key: String, fallback: Int): Int {
val value = getValue(key, Int::class.java)
return value ?: fallback
}
}
When I putInt(key, 123), 123 is autoboxed to java.lang.Integer. When I get value from the Map, how do I compare value typed Any with Class<T> in which T is Int:class.java in this case? Currently, clazz.isInstance(value) always fails. It works if this class is written in Java
I think that's not kotlin but Java. Map only accepts Object type. So the primitive type will be autoboxed to put in a Map. So value returns from Map is alway Object.

Property include/exclude on Kotlin data classes

Suppose I only want one or two fields to be included in the generated equals and hashCode implementations (or perhaps exclude one or more fields). For a simple class, e.g.:
data class Person(val id: String, val name: String)
Groovy has this:
#EqualsAndHashCode(includes = 'id')
Lombok has this:
#EqualsAndHashCode(of = "id")
What is the idiomatic way of doing this in Kotlin?
My approach so far
data class Person(val id: String) {
// at least we can guarantee it is present at access time
var name: String by Delegates.notNull()
constructor(id: String, name: String): this(id) {
this.name = name
}
}
Just feels wrong though... I don't really want name to be mutable, and the extra constructor definition is ugly.
I've used this approach.
data class Person(val id: String, val name: String) {
override fun equals(other: Person) = EssentialData(this) == EssentialData(other)
override fun hashCode() = EssentialData(this).hashCode()
override fun toString() = EssentialData(this).toString().replaceFirst("EssentialData", "Person")
}
private data class EssentialData(val id: String) {
constructor(person: Person) : this(id = person.id)
}
This approach may be suitable for property exclusion:
class SkipProperty<T>(val property: T) {
override fun equals(other: Any?) = true
override fun hashCode() = 0
}
SkipProperty.equals simply returns true, which causes the embeded property to be skipped in equals of parent object.
data class Person(
val id: String,
val name: SkipProperty<String>
)
I also don't know "the idomatic way" in Kotlin (1.1) to do this...
I ended up overriding equals and hashCode:
data class Person(val id: String,
val name: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
}
}
Isn't there a "better" way?
This builds on #bashor's approach and uses a private primary and a public secondary constructor. Sadly the property to be ignored for equals cannot be a val, but one can hide the setter, so the result is equivalent from an external perspective.
data class ExampleDataClass private constructor(val important: String) {
var notSoImportant: String = ""
private set
constructor(important: String, notSoImportant: String) : this(important) {
this.notSoImportant = notSoImportant
}
}
Here's a somewhat creative approach:
data class IncludedArgs(val args: Array<out Any>)
fun includedArgs(vararg args: Any) = IncludedArgs(args)
abstract class Base {
abstract val included : IncludedArgs
override fun equals(other: Any?) = when {
this identityEquals other -> true
other is Base -> included == other.included
else -> false
}
override fun hashCode() = included.hashCode()
override fun toString() = included.toString()
}
class Foo(val a: String, val b : String) : Base() {
override val included = includedArgs(a)
}
fun main(args : Array<String>) {
val foo1 = Foo("a", "b")
val foo2 = Foo("a", "B")
println(foo1 == foo2) //prints "true"
println(foo1) //prints "IncludedArgs(args=[a])"
}
Reusable solution: to have an easy way to select which fields to include in equals() and hashCode(), I wrote a little helper called "stem" (essential core data, relevant for equality).
Usage is straightforward, and the resulting code very small:
class Person(val id: String, val name: String) {
private val stem = Stem(this, { id })
override fun equals(other: Any?) = stem.eq(other)
override fun hashCode() = stem.hc()
}
It's possible to trade off the backing field stored in the class with extra computation on-the-fly:
private val stem get() = Stem(this, { id })
Since Stem takes any function, you are free to specify how the equality is computed. For more than one field to consider, just add one lambda expression per field (varargs):
private val stem = Stem(this, { id }, { name })
Implementation:
class Stem<T : Any>(
private val thisObj: T,
private vararg val properties: T.() -> Any?
) {
fun eq(other: Any?): Boolean {
if (thisObj === other)
return true
if (thisObj.javaClass != other?.javaClass)
return false
// cast is safe, because this is T and other's class was checked for equality with T
#Suppress("UNCHECKED_CAST")
other as T
return properties.all { thisObj.it() == other.it() }
}
fun hc(): Int {
// Fast implementation without collection copies, based on java.util.Arrays.hashCode()
var result = 1
for (element in properties) {
val value = thisObj.element()
result = 31 * result + (value?.hashCode() ?: 0)
}
return result
}
#Deprecated("Not accessible; use eq()", ReplaceWith("this.eq(other)"), DeprecationLevel.ERROR)
override fun equals(other: Any?): Boolean =
throw UnsupportedOperationException("Stem.equals() not supported; call eq() instead")
#Deprecated("Not accessible; use hc()", ReplaceWith("this.hc(other)"), DeprecationLevel.ERROR)
override fun hashCode(): Int =
throw UnsupportedOperationException("Stem.hashCode() not supported; call hc() instead")
}
In case you're wondering about the last two methods, their presence makes the following erroneous code fail at compile time:
override fun equals(other: Any?) = stem.equals(other)
override fun hashCode() = stem.hashCode()
The exception is merely a fallback if those methods are invoked implicitly or through reflection; can be argued if it's necessary.
Of course, the Stem class could be further extended to include automatic generation of toString() etc.
Simpler, faster, look at there, or into the Kotlin documentation.
https://discuss.kotlinlang.org/t/ignoring-certain-properties-when-generating-equals-hashcode-etc/2715/2
Only fields inside the primary constructor are taken into account to build automatic access methods like equals and so on. Do keep the meaningless ones outside.
Here is another hacky approach if you don't want to touch the data class.
You can reuse the entire equals() from data classes while excluding some fields.
Just copy() the classes with fixed values for excluded fields:
data class Person(val id: String,
val name: String)
fun main() {
val person1 = Person("1", "John")
val person2 = Person("2", "John")
println("Full equals: ${person1 == person2}")
println("equals without id: ${person1.copy(id = "") == person2.copy(id = "")}")
}
Output:
Full equals: false
equals without id: true
Consider the following generic approach for the implementation of equals/hashcode. The code below should have no performance impact because of the use of inlining and kotlin value classes:
#file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package org.beatkit.common
import kotlin.jvm.JvmInline
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class HashCode(val value: Int = 0) {
inline fun combineHash(hash: Int): HashCode = HashCode(31 * value + hash)
inline fun combine(obj: Any?): HashCode = combineHash(obj.hashCode())
}
#Suppress("NOTHING_TO_INLINE")
#JvmInline
value class Equals(val value: Boolean = true) {
inline fun combineEquals(equalsImpl: () -> Boolean): Equals = if (!value) this else Equals(equalsImpl())
inline fun <A : Any> combine(lhs: A?, rhs: A?): Equals = combineEquals { lhs == rhs }
}
#Suppress("NOTHING_TO_INLINE")
object Objects {
inline fun hashCode(builder: HashCode.() -> HashCode): Int = builder(HashCode()).value
inline fun hashCode(vararg objects: Any?): Int = hashCode {
var hash = this
objects.forEach {
hash = hash.combine(it)
}
hash
}
inline fun hashCode(vararg hashes: Int): Int = hashCode {
var hash = this
hashes.forEach {
hash = hash.combineHash(it)
}
hash
}
inline fun <T : Any> equals(
lhs: T,
rhs: Any?,
allowSubclasses: Boolean = false,
builder: Equals.(T, T) -> Equals
): Boolean {
if (rhs == null) return false
if (lhs === rhs) return true
if (allowSubclasses) {
if (!lhs::class.isInstance(rhs)) return false
} else {
if (lhs::class != rhs::class) return false
}
#Suppress("unchecked_cast")
return builder(Equals(), lhs, rhs as T).value
}
}
With this in place, you can easily implement/override any equals/hashcode implementation in a uniform way:
data class Foo(val title: String, val bytes: ByteArray, val ignore: Long) {
override fun equals(other: Any?): Boolean {
return Objects.equals(this, other) { lhs, rhs ->
this.combine(lhs.title, rhs.title)
.combineEquals { lhs.bytes contentEquals rhs.bytes }
// ignore the third field for equals
}
}
override fun hashCode(): Int {
return Objects.hashCode(title, bytes) // ignore the third field for hashcode
}
}
You can create an annotation that represents the exclusion of the property as #ExcludeToString or with #ToString(Type.EXCLUDE) parameters by defining enum.
And then using reflection format the value of the getToString().
#Target(AnnotationTarget.FIELD)
#Retention(AnnotationRetention.RUNTIME)
annotation class ExcludeToString
data class Test(
var a: String = "Test A",
#ExcludeToString var b: String = "Test B"
) {
override fun toString(): String {
return ExcludeToStringUtils.getToString(this)
}
}
object ExcludeToStringUtils {
fun getToString(obj: Any): String {
val toString = LinkedList<String>()
getFieldsNotExludeToString(obj).forEach { prop ->
prop.isAccessible = true
toString += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
}
return "${obj.javaClass.simpleName}=[${toString.joinToString(", ")}]"
}
private fun getFieldsNotExludeToString(obj: Any): List<Field> {
val declaredFields = obj::class.java.declaredFields
return declaredFields.filterNot { field ->
isFieldWithExludeToString(field)
}
}
private fun isFieldWithExludeToString(field: Field): Boolean {
field.annotations.forEach {
if (it.annotationClass == ExcludeToString::class) {
return true
}
}
return false
}
}
GL
Gist