Kotlin: universal data type in HashMap? - kotlin

I have some Hash Maps of some objects <String, Object>, some goes like this:
val mapA = HashMap<String, A>
val mapB = HashMap<String, B>
val mapC = HashMap<String, C>
I want to create a function that takes any Hash Map to print it out. This is what I mean:
fun printHashMap(hashMap: HashMap<String, (any type here, for example A, B or C)>){
// print each element of hashmap
}
What I've tried so far:
fun printHashMap(hashMap: HashMap<Any, Any>){
// print HashMap here
}
But it throws me Type mismatch, <Any, Any> vs <String, A> for example.
Is this possible in Kotlin?

If you don't need specific print format, you can print hashMap without your own function:
val testMap = HashMap<String, Any>()
testMap["1"] = 1
testMap["2"] = "2"
print(testMap)
Otherwise, you should change your signature of method to
fun printHashMap(map: Map<String, *>)
In functions better to use Interface instead of concrete class. And instead of Any you need to use star-projections. It is the safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type would be a subtype of that projection.
You can read more about it here and here

Related

How to create a custom iterator in kotlin and add to existing class?

Hello I am trying to add a custom iterator for example to a Pair class from kotlin package to be able to use instance of that class in a for loop
Let's assume this is what I want to be able to do:
val pair: Pair<Int, String> = Pair(1, "sample data")
for (element in pair) {
println(element)
}
I know that there are plenty other ways to print elements from a pair but I specifically want to be able to use pair in a for loop and I need to add iterator() object with next() and hasNext() methods implementation to Pair class
You can do this by providing the iterator() operator for your object, either as a member function or extension function. Example using an extension function:
fun main() {
val pair: Pair<Int, String> = Pair(1, "sample data")
for (element in pair) {
println(element)
}
}
operator fun <T> Pair<T, T>.iterator(): Iterator<T> = listOf(first, second).iterator()
However, you need to be aware that this way you partially lose strongly typing. element can only be a common supertype of all elements, in most cases simply Any?.
You can read more about this in the official documentation: https://kotlinlang.org/docs/control-flow.html#for-loops

Map get with null key

I'm confused by Kotlin's null safety features when it comes to maps. I have a Map<String, String>. Yet I can call map.get(null) and it returns null to indicate that the key is not present in the map. I expected a compiler error, because map is a Map<String, String> and not a Map<String?, String>. How come I can pass null for a String argument?
And a related question: is there any type of Map, be it a stdlib one or a third-party implementation, that may throw NullPointerException if I call get(null)? I'm wondering if it is safe to call map.get(s) instead of s?.let { map.get(it) }, for any valid implementation of Map.
Update
The compiler does indeed return an error with map.get(null). But that is not because of null safety, but because the literal null doesn't give the compiler an indication of the type of the parameter being passed. My actual code is more like this:
val map: Map<String, String> = ...
val s: String? = null
val t = map.get(s)
The above compiles fine, and returns null. How come, when the key is supposed to be a String which is non-nullable?
The get method in Map is declared like this:
abstract operator fun get(key: K): V?
so for a Map<String, String>, its get method should only take Strings.
However, there is another get extension function, with the receiver type of Map<out K, V>:
operator fun <K, V> Map<out K, V>.get(key: K): V?
The covariant out K is what makes all the difference here. Map<String, String> is a kind of Map<out String?, String>, because String is a subtype of String?. As far as this get is concerned, a map with dogs as its keys "is a" map with animals as its keys.
val notNullableMap = mapOf("1" to "2")
// this compiles, showing that Map<String, String> is a kind of Map<out String?, String>
val nullableMap: Map<out String?, String> = notNullableMap
And this is why you can pass in a String? into map.get, where map is a Map<String, String>. The map gets treated as "a kind of" Map<String?, String> because of the covariant out K.
And a related question: is there any type of Map, be it a stdlib one or a third-party implementation, that may throw NullPointerException if I call get(null)?
Yes, on the JVM, TreeMap (with a comparator that doesn't handle nulls) doesn't support null keys. Compare:
val map = TreeMap<Int, Int>()
println(map[null as Int?]) // exception!
and:
val map = TreeMap<Int, Int>(Comparator.nullsLast(Comparator.naturalOrder()))
println(map[null as Int?]) // null
However, note that since the problematic get is an extension function that is available on every Map, you cannot prevent someone from passing in a nullable thing to your map at compile time, as long as your map implements Map.

Case insensitive key for hashmap kotlin?

I have a hashmap with string as key and integer as value. I want the keys to be case insensitive.
val items = HashMap<String, Int>()
items["key1"] = 90
items["Key1"] = 80
items["C"] = 70
for ((k, v) in items) {
println("$k = $v")
}
This takes key1 and Key1 as separate entries
For this, you would need either to provide some extension function that would put and get the entry in some defined way (e.g. using every time lowercase() String's method) keeping keys case insensitive
fun HashMap<String, Int>.putInsensitive(k: String, v: Int) {
this[k.lowercase()] = v
}
fun HashMap<String, Int>.getInsensitive(k: String, v: Int): Int? = this[k.lowercase()]
or provide your own Map interface implementation (it could even inherit from HashMap)
class InsensitiveHashMap<V> : HashMap<String, V>() {
override fun put(key: String, value: V): V? = super.put(key.lowercase(), value)
override fun get(key: String): V? = return super.get(key.lowercase())
}
Although you can fake this with an extension to HashMap (as mentioned in another answer), I don't think that's a good solution: in order to make the map's behaviour fully consistent, you'd probably need to override lots of methods.  (For example, it would be hard to ensure that it maintained the case-insensitivity if you update the map via its keySet.  And there are probably many more gotchas along the way.)
In most cases, you don't really need a HashMap specifically — you just need a Map implementation.  And so an alternative is to use another type of map that lets you provide your own Comparator, e.g.:
val items = TreeMap<String, Int>{ a, b ->
a.toLowerCase().compareTo(b.toLowerCase())
}
TreeMap is an implementation of SortedMap — a special type of Map that keeps its keys in order: either their natural ordering, or one you provide.  In this case, I've given a simple Comparator implementation which compares the lower-case versions of the two strings.
With this definition, the rest of the code in the question runs fine, and prints out:
C = 70
key1 = 80
…i.e. it has recognised that "Key1" should be treated the same as "key1", and updated that value instead of adding a new one.  I think this is the behaviour you want.
You don't need to take account of the fact that the map is sorted; that's just an added bonus.  You can still treat it like any other Map implementation, and everything should work.
(This is yet another example of why it's better to program to the interface, not the implementation.  If you write code that can use any Map implementation, you can give it a HashMap or a TreeMap or any other sort of map without any changes.)
A variation on the answer by #m.antkowicz, you can actually "override" the get / put operators on HashMap itself (and make it into a fantastic footgun):
operator fun HashMap<String, Int>.get(k: String): Int? = this[k.toLowerCase()]
operator fun HashMap<String, Int>.set(k: String, v: Int): Int? {
val originalK = this[k.toLowerCase()]
this.put(k.toLowerCase(), v )
return originalK
}
So now you can:
fun main() {
val items = HashMap<String, Int>()
items["key1"] = 90
items["Key1"] = 80
items["C"] = 70
for ((k, v) in items) {
println("$k = $v")
}
// Prints:
// key1 = 80
// c = 70
}

Type- or Class-keyed map

In Kotlin I can use filterIsInstance to obtain a type-specific (and type-safe) sub-collection:
val misc: List<Any> = listOf(42, 3.14, true, "foo", "bar")
val strings: List<String> = misc.filterIsInstance<String>()
println(strings) // => [foo, bar]
But I have a large collection of objects and I would like to pre-sort it into a Map, by concrete type. Is it even possible to define such a map in Kotlin's type system?
val miscByType: Map<KType, Collection<???>>
or:
val miscByClass: Map<KClass, Collection<???>>
Should I use a custom implementation with unsafe (but logically sound) casts?
The following is such an implementation. It works, but I'm wondering if there is a less hacky way of doing it:
import kotlin.reflect.KClass
class InstanceMap {
// INVARIANT: map from KClass to a set of objects of *that concrete class*
private val map: MutableMap<KClass<*>, MutableSet<Any>> = mutableMapOf()
// this is the only public mutator, it guarantees the invariant
fun add(item: Any): Boolean =
map.getOrPut(item::class) { mutableSetOf() }.add(item)
// public non-inline accessor, only needed by the inline accessor
fun get(cls: KClass<*>): Set<*>? = map[cls]
// inline accessor that performs an unsafe, but sound, cast
#Suppress("UNCHECKED_CAST")
inline fun <reified T> get(): Set<T> = get(T::class) as Set<T>? ?: setOf()
}
fun instanceMapOf(vararg items: Any): InstanceMap = InstanceMap().apply {
items.forEach { add(it) }
}
val misc = instanceMapOf(42, 3.14, true, "foo", "bar")
val strings = misc.get<String>()
println(strings) // => [foo, bar]
Your code looks OK. The only problem is with the unchecked cast warning. At the JVM bytecode level, the cast does nothing, because of the way, how generics are implemented in Java and Kotlin. It is also known as type erasure.
https://en.wikipedia.org/wiki/Type_erasure
Type erasure adds yet another problem to your code - it does not tell generic type arguments. So that, for example, List<Int> has the same class as List<String> or List<Map<String, Object>>
Do you expect your code to find superclasses or interfaces in the map? E.g. if I have
interface A
interface B
class C : A, B
val m = InstanceMap()
m.add(C())
m.get(C::class)
m.get(A::class)
m.get(B::class)
Do you expect these 3 calls to return the same value?
The JVM standard workaround for it is to explicitly pass Class<T> parameter and use the Class#cast method to cast instead. That change will make the code safer.
There is a remedy to type erasure in Kotlin. You may add a reified inline function so that Kotlin compiler will use the exact type in the inlined generic function body
https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters
inline fun <reified T> InstanceMap.get() = get(T::class)

obtain class from nested type parameters in kotlin

I have a val built like this
val qs = hashMapOf<KProperty1<ProfileModel.PersonalInfo, *> ,Question>()
How can I obtain the class of ProfileModel.PersonalInfo from this variable?
In other words what expression(involving qs of course) should replace Any so that this test passes.
#Test
fun obtaionResultTypeFromQuestionList(){
val resultType = Any()
assertEquals(ProfileModel.PersonalInfo::class, resultType)
}
Thank you for your attention
There is no straight way to get such information due to Java type erasure.
To be short - all information about generics (in your case) is unavailable at runtime and HashMap<String, String> becomes HashMap.
But if you do some changes on JVM-level, like defining new class, information about actual type parameters is kept. It gives you ability to do some hacks like this:
val toResolve = object : HashMap<KProperty1<ProfileModel.PersonalInfo, *> ,Question>() {
init {
//fill your data here
}
}
val parameterized = toResolve::class.java.genericSuperclass as ParameterizedType
val property = parameterized.actualTypeArguments[0] as ParameterizedType
print(property.actualTypeArguments[0])
prints ProfileModel.PersonalInfo.
Explanation:
We define new anonymous class which impacts JVM-level, not only runtime, so info about generic is left
We get generic supperclass of our new anonymous class instance what results in HashMap< ... , ... >
We get first type which is passed to HashMap generic brackets. It gives us KProperty1< ... , ... >
Do previous step with KProperty1
Kotlin is tied to the JVM type erasure as well as Java does. You can do a code a bit nice by moving creation of hash map to separate function:
inline fun <reified K, reified V> genericHashMapOf(
vararg pairs: Pair<K, V>
): HashMap<K, V> = object : HashMap<K, V>() {
init {
putAll(pairs)
}
}
...
val hashMap = genericHashMapOf(something to something)