4

Assuming I have A and B:

data class A(val a: String, val common: String)
data class B(val b: String, val common: String)

Can I have a method that accepts either one and use common in it? something like:

fun accept(val param: A|B) {
    println(param.common)
}
David Soroko
  • 8,521
  • 2
  • 39
  • 51
orirab
  • 2,915
  • 1
  • 24
  • 48
  • 1
    To spell out one point: the reason you can't just use `common` is that `A`'s `common` is unrelated to `B`'s `common`.  The two properties may be _spelled_ the same, but that's not significant; in Kotlin, they're completely different, because there's no inheritance relationship between them.  That relationship is what all the answers are providing, in various ways. – gidds Nov 03 '20 at 11:29

3 Answers3

4

Sealed classes do pretty much that:

sealed class AorB (val common : String)
class A(val a: String, common: String) : AorB(common)
class B(val b: String, common: String) : AorB(common)

fun accept(param: AorB) {
    println(param.common)
    when(param) {
        is A -> println(param.a)
        is B -> println(param.b)
    }
}

fun main() {
    accept(A("a value", "common value")) 
    accept(B("b value", "common value")) 
}

David Soroko
  • 8,521
  • 2
  • 39
  • 51
3

You can do it if you have them both implement a common interface, and use the interface as the type of parameter:

interface HasCommon {
    val common: String
}

data class A(val a: String, override val common: String) : HasCommon
data class B(val b: String, override val common: String) : HasCommon

fun accept(param: HasCommon) {
    println(param.common)
}

fun main() {
    accept(A("a", "a"))
    accept(B("b", "b"))
}

Output:

a
b

Alternatively, you could do it in a more functional way by defining multiple versions of accept for each type, and abstracting out the common functionality:

data class A(val a: String, val common: String)
data class B(val b: String, val common: String)

fun accept(a: A) = printCommon(a.common)
fun accept(b: B) = printCommon(b.common)

fun printCommon(common: String) = println(common)

fun main() {
    accept(A("a", "a"))
    accept(B("b", "b"))
}
Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
  • 1
    This works, but having the parameter be called `common` is purely cosmetic. They have no more in common then `a`or `b` – David Soroko Nov 03 '20 at 12:05
  • Assuming you're talking about the 2nd example. The first one is enforced by the interface. Although I think I prefer the 2nd one now. You could enforce it by making them extension functions: `fun A.printCommon() = printCommon(common)`. – Adam Millerchip Nov 03 '20 at 12:11
1

Inheritance is supposed to fix this problem

After all, accepting either would mean you can't call any specific methods on them unless you cast them.

That would be the same thing as accepting "Any" in Kotlin.

A workaround would be to define 2 methods where one method could call the other one after casting it.

Or you make the classes inherit but you are using data classes so I assume you already know that that isn't easily possible with them.

A workaround for inheritance with data classes is this

Merthan Erdem
  • 5,598
  • 2
  • 22
  • 29