[Newbie to Kotlin question] Accessing sub-class method

I created a list of classes and sub-classes making up a “Creature” that can be human or alien with multiple appendages and a head.
The class structure is not erroring however, I am unable to access a method think() within the sub-class Human:Creature → Head : Appendage.

Dumping the path I can see that I am able to access the Head class but just not the think() method:

    val human = Human()
    println(human.appendages["Head"])           **// [Head@3803641]**
    println(human.appendages["Head"]?.get(0))   **// Head@3803641**
    human.appendages["Head"]?.get(0).think()    **// uncaught reference**

Here’s my list of classes ( sorry for all the code but, it gives context to my problem )

import org.omg.CORBA.Object
import java.util.*

open class Creature {
    var appendages = HashMap<String,ArrayList<Appendage>>()
}
open class Appendage {
    constructor() {}
}
open class Human : Creature {
    constructor()
    {
        appendages["Arm"] = ArrayList<Appendage>()
        appendages["Arm"]?.add(Arm())
        appendages["Arm"]?.add(Arm())
        appendages["Leg"] = ArrayList<Appendage>()
        appendages["Leg"]?.add(Leg())
        appendages["Leg"]?.add(Leg())
        appendages["Head"] = ArrayList<Appendage>()
        appendages["Head"]?.add(Head("Human"))
    }
}
class Alien : Creature
{
    constructor()
    {
        appendages["Tentacle"] = ArrayList<Appendage>()
        appendages["Tentacle"]?.add(Tentacle())
        appendages["Tentacle"]?.add(Tentacle())
        appendages["Tentacle"]?.add(Tentacle())
        appendages["Tentacle"]?.add(Tentacle())
        appendages["Arm"] = ArrayList<Appendage>()
        appendages["Arm"]?.add(Arm())
        appendages["Arm"]?.add(Arm())
        appendages["Leg"] = ArrayList<Appendage>()
        appendages["Leg"]?.add(Leg())
        appendages["Leg"]?.add(Leg())
        appendages["Head"] = ArrayList<Appendage>()
        appendages["Head"]?.add(Head("Alien"))
        appendages["Head"]?.add(Head("Alien"))
    }
}

class Head : Appendage {
    private var whatami : String = ""
    constructor(whatami : String)
    {
        this.whatami = whatami
    }
    public fun think()
    {
        println("$whatami Thinking")
    }
}
open class Arm : Appendage {
    private var rotation = mutableListOf<Int>(0,0,0)
    constructor() {}
    fun rotateX(degrees: Int)
    {
        rotation[0] = degrees
    }
    fun rotateY(degrees: Int)
    {
        rotation[1] = degrees
    }
    fun rotateZ(degrees: Int)
    {
        rotation[2] = degrees
    }
}
open class Leg : Appendage {
    private var rotation = mutableListOf<Int>(0,0,0)
    constructor() {}
    fun rotateX(degrees: Int)
    {
        rotation[0] = degrees
    }
    fun rotateY(degrees: Int)
    {
        rotation[1] = degrees
    }
    fun rotateZ(degrees: Int)
    {
        rotation[2] = degrees
    }
}
open class Tentacle : Appendage {
    private var rotation = mutableListOf<Int>(0,0,0)
    constructor() {}
    fun rotateX(degrees: Int)
    {
        rotation[0] = degrees
    }
    fun rotateY(degrees: Int)
    {
        rotation[1] = degrees
    }
    fun rotateZ(degrees: Int)
    {
        rotation[2] = degrees
    }
}

The problem is that appendages["head"]?.get(0) returns a value of type Appendage.
You know that it is a Head since you only put heads in there, but the compiler doesn’t. It thinks it could be any appendage so you need to add a type cast appendages["head"]?.get(0) as Head.
If you not sure what type you have you can use a when expression.

when(someAppendage) {
   is Head -> TODO()
   is Arm -> TODO()
   else -> TODO() 
}

Awesome!
Thank you so much for your help.

I will experiment with “when” as you have suggested but, in the meantime (as a proof of concept) this works as well.

    val head = human.appendages["Head"]?.get(0) as Head
    head.think()    // prints "Human Thinking"

Hi @jheminger,
I submit you an alternative implementation, for funny.
In such case the compiler can infer the type.
It works with Human and Alien.

(I removed some methods for conciseness)

fun main() {
    val creature = Human()
    creature.appendages.filterIsInstance<Head>().forEach { it.think() }
}

open class Creature(val appendages: List<Appendage>) {
    constructor(vararg appendages: Appendage) : this(appendages.asList())
}

open class Appendage

class Human : Creature(Arm(), Arm(), Leg(), Leg(), Head("Human"))

class Alien : Creature(Tentacle(), Tentacle(), Tentacle(), Tentacle(), Arm(), Arm(), Leg(), Leg(), Head("Alien"), Head("Alien"))

data class Head(val whatami: String) : Appendage() {
    fun think() {
        println("$whatami Thinking")
    }
}

class Arm : Appendage()
class Leg : Appendage()
class Tentacle : Appendage()

Thanks @fvasco,
Much cleaner. This will help me learn.

@fvasco

After some evaluation I decided to keep my original code the way it was originally because I like being able to target the appendage by name then index.

   val human = Human()
    (human.appendages["Head"]?.get(0) as Head).think()
    val alien = Alien()
    (alien.appendages["Head"]?.get(0) as Head).think()
    (alien.appendages["Head"]?.get(1) as Head).think()

Thanks again everybody!

    creature.appendages.filterIsInstance<Head>().get(0).think()

or use a utility method


fun main() {
    val creature = Human()
    creature.appendages<Head>().first().think()
}

inline fun <reified T> Creature.appendages() = appendages.filterIsInstance<T>()

I just proposing you some ideas :wink:

Appreciated!