Matthew Layton
Matthew Layton

Reputation: 42229

Kotlin - return type of current instance

I am building a validation library in Kotlin. The base class is abstract and implements methods that apply to all types; isNotNull for example:

abstract class Validator<V>(protected val value: V?) {
    fun isNotNull(): Validator<V> {
        if(value == null) {
            // ... some validation handling here ...
        }

        return this
    }
}

Then I am sub-classing validators for specific types:

class IntValidator(value: Int?) : Validator<Int>(value) {
    fun isNotZero(): IntValidator {
        if(value == 0) {
            // ... some validation handling here ...
        }

        return this
    }
}

Now say I want to validate that an Int? is not null and not zero

val validator = IntValidator(myNullableInteger)

validator
    .isNotNull()
    .isNotZero()

The code above does not work, because .isNotNull() returns Validator<V>, rather than IntValidator, so .isNotZero() is no longer in scope.

Is there a way for methods to return the type that instantiated them (in my case, I want it to return IntValidator, not Validator<T>)?

Upvotes: 3

Views: 1032

Answers (3)

s1m0nw1
s1m0nw1

Reputation: 81859

Maybe you should reconsider the API design. What about not chaining the methods and using scope functions instead?

val validator = IntValidator(myNullableInteger)
with(validator) {
    isNotNull()
    isNotZero()
}

In the scope of an IntValidator, both methods will be accessible.

Upvotes: 3

Mike Hill
Mike Hill

Reputation: 3772

As discussed on this kotlinlang thread, you can use extension methods for this:

abstract class Validator<V>(internal val value: V?)
fun <T: Validator<U>, U> T.isNotNull(): T {
    if(this.value == null) {
        // ... some validation handling here ...
    }
    return this
}

class IntValidator(value: Int?) : Validator<Int>(value)

@Suppress("FINAL_UPPER_BOUND")
fun <T: IntValidator> T.isNotZero(): T {
    if (this.value == 0) {
        // ... some validation handling here ...
    }
    return this
}

fun main() {
    val validator = IntValidator(0)
    validator
            .isNotNull()
            .isNotZero()
}

This lets you use the object type as a generic parameter on the function, meaning that you can receive the object type as the result as well. The generic bounds allow you to use type-safety while returning the calling instance's type.

Upvotes: 2

Kevin Robatel
Kevin Robatel

Reputation: 8386

One way, but with an unchecked cast:

fun main(args: Array<String>) {
  val validator = IntValidator(2)

  validator
    .isNotNull()
    .isNotZero()
}

abstract class Validator<V, out R>(protected val value: V?) {
    open fun isNotNull(): R {
        if(value == null) {
            // ... some validation handling here ...
        }

        return this as R
    }
}

class IntValidator(value: Int?) : Validator<Int, IntValidator>(value) {
    fun isNotZero(): IntValidator {
        if(value == 0) {
            // ... some validation handling here ...
        }

        return this
    }
}

Upvotes: 2

Related Questions