How to use Kotlin generics to access same fields in sibling classes

108 views Asked by At
data class Type1(val str: String)
data class Type2(val str: String)

interface Person

data class Child1(val name: Type1) : Person
data class Child2(val name: Type2) : Person

fun main() {
    val foo = Child1(Type1("foo"))
    val bar = Child2(Type2("bar"))

    printName(foo)
    printName(bar)
}
    
fun printName(person: Person) {

    // Option 1: would like to do this
    // println(person.name)  // Unresolved reference: name

    // Option 2: works but if I have lots of other code,
    //   it's unnecessary duplication of code
    when (person) {
        is Child1 -> {
            println(person.name)
            // lots of other code
        }
        is Child2 -> {
            println(person.name)
            // lots of other code
        }
    }
}

Inside printName(), I would like to be able to use a single println(person.name) call and have it print the name of either Child1 or Child2, whichever is passed in. What are the different ways I can make this happen in Kotlin?

UPDATE:

  1. I don't own the base classes so not able to change the inheritance.
  2. The names of each child are of different types.
2

There are 2 answers

4
Muhammad Rio On

You can create an extension function if you don't own those classes

data class Type1(val str: String)
data class Type2(val str: String)

interface Person

data class Child1(val name: Type1) : Person

data class Child2(val name: Type2) : Person

// extension
fun Person.getNameStr() : String {
    return when(this){
        is Child1 -> name.str
        is Child2 -> name.str
        else -> ""
    }
}

// or

val Person.nameStr : String
    get() = when(this){
        is Child1 -> name.str
        is Child2 -> name.str
        else -> ""
    }

fun printName(person: Person) {
    println(person.getNameStr())
    //or
    println(person.nameStr)
}

Importance

this extension will not valid if you want to return the class itself (Type1 and Type2)

1
Demigod On

You need to have name in your interface, like

interface Person {
    val name: String
}

data class Child1(override val name: String) : Person

data class Child2(override val name: String) : Person

to be able to get it from other Child classes...

If not every child class that extends Person will contain name, you can introduce an intermediate interface, like

interface Person

interface NamedPerson : Person {
    val name: String
}

data class Child1(override val name: String) : NamedPerson

data class Child2(override val name: String) : NamedPerson

In any other case, there should be an instance check, like

   when (person) {
        is Child1 -> println(person.name)
        is Child2 -> println(person.name)
    }