Accessing fields from the values of a Map in Kotlin fails - kotlin

I am having the following two classes:
class A() {
val name = "A"
}
class B() {
val name = "B"
}
Then I create a map with instances of those two classes as values of the Map.
var m = mapOf("1" to A(), "2" to B())
Then I try to access the field name from the values of this map.
for ((k, v) in m) {
println(v.name)
}
And I get this error:
error: unresolved reference: name
println(v.name)
How do I access the field, and why do I get this error?

Because you map to two different classes it can only conclude that you are mapping to the class Any which is the only class they both share, which doesn't have the field name. To solve it you can let both classes implement a common interface, like this
interface HasName {
val name : String
}
class A : HasName {
override val name = "A"
}
class B : HasName {
override val name = "B"
}
After this the compiler recognizes that you are mapping to objects that implement HasName and your code will work fine

Ok first you need to know that map contains a key and a value and each one must have a type for example: (Map<String, Int> so the key must be a string and the value must be an Int).
Now for you case the map is: var m = mapOf("1" to A(), "2" to B())
the key is of type String but the value is of type A in the first element and of type B in the second element, so the compiler will specify the type Any to the value so your map is of type: Map<String, Any>, that's why when you try to access v.name you can't because in type Any there nothing called name.
To fix this problem you can use inheritance, you can create an interface:
interface X {
val name: String
}
class A(): X {
override val name = "A"
}
class B(): X {
override val name = "B"
}
So now your map will be of type Map<String, X> because both A and B are of type X and you will be able to get access to name

Related

Kotlin: Hashmap of interface methods by the implementing class name

I have a list of clases that implement a specific interface. The ability to construct those clases or not is not static (so it's not possible to use when(className)), and can be configured so I want to be able to create some clases or call some methods based on a hashMap of allowed "constructors". Then if the key identifying a class is in present in the hashmap I can call the corresponding method, otherwise I can safely ignore. Let me illustrate:
Let's say I have an interface like
interface Instanceable {
data class Config(
val bar: Whatever
)
fun getIntance(config: Config): Instanceable
}
Then I have several (let's say 10) classes that implement this interface
class Implementation1() : Instanceable {
companion object {
const val ID = "INSTANCE_1"
}
private lateinit var foo: Whatever
override fun getIntance(config: Config) = Implementation1().also{ this#Implementation1.foo = config.bar }
}
I want to create a hashmap of the methods by the identifiers, so later down the lane I can grab the method from the hashMap by the key ID and just invoke() the value if it's there. Something like:
allowedInstances("INSTANCE_1")?.let{ it.invoke(someConfig) }
In order to do this I tried to create a hashMap of methods like this:
private val allowedInstances = mutableHashMapOf<String, Instanceable.(Instanceable.Config)->Instanceable>()
allowedInstances[Instance1.ID] = Instance1::getIntance
allowedInstances[Instance2.ID] = Instance2::getIntance
allowedInstances[Instance4.ID] = Instance4::getIntance
But it fails with:
Type mismatch.
Required: Instanceable.(Instanceable.Config) → Instanceable
Found: KFunction2<Implementation1, Instanceable.Config, Instanceable>
If I create the hashmap directly and let the compiler infer the types like this:
private val allowedInstances = mutableHashMapOf(
Implementation1.ID to Implementation1::getIntance,
Implementation2.ID to Implementation2::getIntance,
Implementation4.ID to Implementation4::getIntance,
)
Checking the type of the hashmap shows:
HashMap<String, out KFunction2<Nothing, Instanceable.Config, Instanceable>>
In fact I can do:
private val allowedInstances = mutableHashMapOf<String, Nothing.(Instanceable.Config)->Instanceable>()
allowedInstances[Instance1.ID] = Instance1::getIntance
allowedInstances[Instance2.ID] = Instance2::getIntance
allowedInstances[Instance4.ID] = Instance4::getIntance
So the actual question is:
Why the function of the second hashMap parameter has Nothing as the receptor? Why I cannot have the interface Instanceable instead?
Edit: Still not good to have the Nothing there:
allowedInstances["INSTANCE_1"]?.let{ it.invoke(Nothing, someConfig) }
//Fails with: Classifier 'Nothing' does not have a companion object, and thus must be initialized here
Edit 2: All of the errors are in compile time
Your function type
Instanceable.(Instanceable.Config) -> Instanceable
is describing an extension function on an instance of Instanceable. You need to omit the receiver from the function type to be able to match your constructors' signature:
(Instanceable.Config) -> Instanceable
Edit: The other half of the problem is that you define getInstance() as a member function of the class. So you have to create an invalid instance of your class to use to create a valid instance, which doesn't make sense.
I would delete the getInstance() function from your interface, and put the equivalent code in the constructor of your class. Then you can define a function type in your Map that constructs your items.
interface Instanceable {
data class Config(
val bar: Whatever
)
// REMOVE this: fun getIntance(config: Config): Instanceable
}
class Implementation1(config: Config) : Instanceable {
companion object {
const val ID = "INSTANCE_1"
}
private val foo: Whatever = config.bar
}
private val allowedInstances = mutableHashMapOf<String, (Instanceable.Config)->Instanceable>()
allowedInstances[Instance1.ID] = ::Implementation1
// and so on...
// If there's an implementation that has no config, you can use a lambda:
class NoConfigImplementation : Instanceable {
companion object {
const val ID = "INSTANCE_2"
}
}
allowedInstances[NoConfigImplementation.ID] = { _ -> NoConfigImplementation() }

How to change return type based on a function input which is a class name?

I have multiple data classes and each class has a corresponding class containing more info. I want to write a function in which I should be able to pass an identifier (table name corresponding to the data class). Based on this identifier, object of the corresponding class should be made, the value changed and this object should be returned as output of the function. I have written a simplified version of it on playground but I am unable to get it to work. Any help is appreciated.
class someClass(
)
class objectForSomeClass(
var value: String
)
class someOtherClass(
)
class objectForSomeOtherClass(
var value: String
)
class doSomething() {
companion object {
val classMap = mapOf(
"someClass" to objectForSomeClass::class,
"someOtherClass" to objectForSomeOtherClass::class,
)
}
// Create a map of class name to a new object based on the class name input
fun dummyFun(className: String, valueInput: String): Map<String, kotlin.Any> {
var returnObject = mutableListOf<Pair<String, kotlin.Any>>()
when(className) {
"SOME_CLASS" -> {
returnObject = mutableListOf<Pair<String, justDoIt.classMap["someClass"]()>>()
}
"SOME_OTHER_CLASS" -> {
returnObject = Map<String, justDoIt.classMap["someOtherClass"]()>
}
}
returnObject[className].value = valueInput
return returnObject
}
}
fun main() {
var obj = doSomething()
var t = obj.dummyFun("SOME_CLASS", "Value to be inserted")
// do something with t
}
Not knowing more about your classes (the ones in your code are not data classes – a data class in Kotlin is a specific type of class) I still think a lot could be simplified down to maybe even this:
fun createObject(className: String, value: String): Any? {
return when (className) {
"SomeClass" -> ObjectForSomeClass(value)
"SomeOtherClass" -> ObjectForSomeOtherClass(value)
// ...
else -> null
}
}
Additionally:
The classMap is not necessary, you can hard-code the cases in the when clause as in my example. There is also no need for reflection, which you would need to create instances from SomeType::class.
With getting rid of classMap you also do not need the companion object holding it anymore, and then you are left with one function for creating instances of your classes, and this function does not have to be in a class. You might put it into a singleton class called object in Kotlin (https://kotlinlang.org/docs/object-declarations.html#object-expressions)
Data classes in Kotlin: https://kotlinlang.org/docs/data-classes.html
You could maybe also replace each class someClass & class objectForSomeClass pair with a class someClass with a companion object.

Kotlin: How to specify a named arguent with a variable?

Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)

Reference outside the sealed class in Kotlin?

I'm trying to create a class that uses its own state to operate on the state of an external object that it holds a reference to. The external object can be of class A or B, which are similar, but not controlled by the author. So a sealed class is created to access their common attributes, per this earlier answer from #SimY4.
// *** DOES NOT COMPILE ***
class A { // foreign class whose structure is not modifiable
val prop get()= "some string made the Class-A way"
}
class B { // foreign class whose structure is not modifiable
val prop get()= "some string made the Class-B way"
}
data class ABTool (val obj:AB, val i:Int, val j:Int) {
// class that manipulates i and j and uses them to do
// things with AB's "common" attributes through the sealed class AB
sealed class AB { // substitute for a common interface
abstract val prop: String
abstract val addmagic: String
data class BoxA(val o:A) : AB() {
override val prop get()= o.prop
override val addmagic get() = prop + this#???.magic // HOW TO REFERENCE?
}
data class BoxB(val o:B) : AB() {
override val prop get()= o.prop
override val addmagic get() = this#???.magic + prop // HOW TO REFERENCE?
}
}
val magic get()= "magic: ${i*j}"
}
The problem now is that I've figured out I can't operate on the external object in the way I want, because a sealed class can't refer to its outer class members. Is there a better way to make this work, even if using a different approach (other than sealed class), while:
not changing foreign classes A or B;
respecting that A and B (and many others in the real case) are similar, so I'm trying to write one tool that calculates and adds magic to A and B with the same code base; and
noting that although the ABTool tools are the same, the way they are applied to add magic is slightly different in A vs. B, just as the to access the conceptually common elements of A and B may be different.
Any thoughts on this or a similar workaround? Maybe a more functional approach that I haven't conceived yet?
If ABTool being a sealed class is something you can give up, then here's a solution:
Replace sealed with inner abstract at the ABTool declaration;
Mark BoxA and BoxB as inner as well;
data class ABTool(val obj: AB, val i: Int, val j: Int) {
inner abstract class AB {
abstract val prop: String
abstract val addmagic: String
inner class BoxA(val o: A) : AB() {
override val prop get() = o.prop
override val addmagic get() = prop + magic
}
inner class BoxB(val o: B) : AB() {
override val prop get() = o.prop
override val addmagic get() = magic + prop
}
}
val magic get() = "magic: ${i * j}"
}
(alternatively, instead of marking AB as inner, move BoxA and BoxB out of it to the scope of ABTool)
An alternative would be to add an ABTool field to AB:
sealed class AB(val tool: ABTool) {
abstract val prop: String
abstract val addmagic: String
data class BoxA(val o:A, tool: ABTool) : AB(tool) {
override val prop get()= o.prop
override val addmagic get() = prop + tool.magic
}
data class BoxB(val o:B, tool: ABTool) : AB(tool) {
override val prop get()= o.prop
override val addmagic get() = tool.magic + prop
}
}
and pass this when creating it from ABTool. That's just what inner really does, after all.
In this specific case the field happens to be unused in AB itself and so you can remove it from there and make it val in BoxA and BoxB.

How to instantiate a new instance of generic type

In C# you can place a new constraint on a generic to create a new instance of the generic parameter type, is there an equivalent in Kotlin?
Right now my work around is this:
fun <T> someMethod(class : () -> T) {
val newInstance = class()
}
and I'm calling someMethod() like this
someMethod<MyClass>(::MyClass)
but I would like to do something like this:
fun <T : new> someMethod() {
val newInstance = T()
}
Is that possible?
Currently, that's not possible. You can give a thumbs-up for the issue https://youtrack.jetbrains.com/issue/KT-6728 to vote for the addition of this feature.
At least, you can leave out the generic type because Kotlin can infer it:
someMethod(::MyClass)
A solution:
1/ use an inline function with preserved param type (reified type)
2/ in this inline function, invoque the needed constructor using class introspection (reflexion *)
/!\ an inline function can't be nested/embedded in a class or function
Let see how it works on a simple example:
// Here's 2 classes that take one init with one parameter named "param" of type String
//!\ to not put in a class or function
class A(val param: String) {}
class B(val param: String) {}
// Here's the inline function.
// It returns an optional because it could be passed some types that do not own
// a constructor with a param named param of type String
inline fun <reified T> createAnInstance(value: String) : T? {
val paramType = String::class.createType() //<< get createAnInstance param 'value' type
val constructor = T::class.constructors.filter {
it.parameters.size == 1 && it.parameters.filter { //< filter constructors with 1 param
it.name == "param" && it.type == paramType //< filter constructors whose name is "param" && type is 'value' type
}.size != 0
}.firstOrNull() //< get first item or returned list or null
return constructor?.call(value) // instantiate the class with value
}
// Execute. Note that to path the type to the function val/var must be type specified.
val a: A? = createAnInstance("Wow! A new instance of A")
val b: B? = createAnInstance("Wow! A new instance of B")
*) kotlin-reflect.jar must be included in the project
In Android Studio: add to build.gradle(Module: app): implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"