I’m having trouble with a generic base class for a builder type object. My simplified case is like t...
c
I’m having trouble with a generic base class for a builder type object. My simplified case is like this:
Copy code
abstract class Base<T : Base<T>>() {
    fun doSomething(): T {
        ... do something ...
        return this
    }
}

class Builder : Base<Builder>() {
    fun build(): Something {
        ... return an immutable object ...
    }
}

val something = Builder().doSomething().build()
So I’d like to have a base object, where the derived classes will be builders. However, the
return this
is marked with an error, since (I guess) the compiler can’t prove that all derived classes are instances of
T
. Is there some way I can achieve this, or should I just
return this as T
and suppress the unchecked cast?
y
Easy:
Copy code
abstract class Base<T : Base<T>>() {
  internal fun doSomethingInternal()  { ... }
}
fun <T: Base<T>> T.doSomething(): T = apply { doSomethingInternal() }
c
Interesting, thanks!
s
The problem in your code is that
doSomething()
is a method bound to
Base
class which itself doesn't know about implementations. Using casting from
Base<T>
to
T
is not the way to go here and can lead to unexpected behavior for edge cases. Take a look at following example:
Copy code
abstract class Base<T : Base<T>> {
    fun doSomething(): T {
        return this as T
    }
}

class Builder : Base<Builder>()
class EdgeCase : Base<Nothing>() // Nothing is a subtype of everything, therefore Nothing : EdgeCase

val builderSomething = Builder().doSomething() // returned type is Builder, as expected
val edgeCaseSomething = EdgeCase().doSomething() // returned type is Nothing, not EdgeCase
There are many possible solutions to your problem. If you still need
doSomething
to be a member method of
Base<T>
, then making it not return anything might be a wise choice - since this is a final method declared in the abstract class, you could never make its declared return type to designate actual implementation. However, it would be perfectly fine to call it within some other type-aware scope as for example: `Builder().apply { doSomething() } // returned type is `Builder`` In case you would like
doSomething
to be a part of public API and preserve its caller type as a return type at the same time, you can make it a generic extension function, assuming it doesn't use any private/protected members directly (wildcard in
Base<*>
handles
Base<Nothing>
case, depending on your use case, this may or may not be something you worry about)
Copy code
fun <T : Base<*>> T.doSomething(): T = apply {
    // use API from `Base` class
}

Builder().doSomething() // returned type is Builder
EdgeCase().doSomething() // returned type is EdgeCase
c
Thanks very much for the super detailed reply, I appreciate it. Based on Youssef’s reply, I ended up going with extension methods as you also suggested - it seems like the cleanest option.
❤️ 1