How to deal with generic bounds migrating from Java to Kotlin?

Viewed 121

My goal is to have the interface methods accept the class type of the implementation. Here's the code I have written so far:

internal interface Diff<Diff> {   //in Java I was using <? extends Diff>
    fun lessThan(other: Diff): Boolean
}

private class ADiff(private val value: Int) : Diff<ADiff> {

    override fun lessThan(other: ADiff): Boolean {
        return value < other.value
    }

}

//B can now accept even int types which is not desired
private class BDiff(private val value: Int) : Diff<Int> {

    override fun lessThan(other: Int): Boolean {
        return value < other
    }

}
1 Answers

The reason why this "worked" in Java was because <T extends Diff> uses the raw type Diff. Don't do this!


The closest you can get is to use a recursive type bound:

interface Diff<T : Diff<T>> {
    fun lessThan(other: T): Boolean
}

The problem is, you can substitute any other subtype of Diff.

However, when using the Diff, use the generic type constraint T : Diff<T>:

fun <T : Diff<T>> diffUser(diff1: T, diff2: T) {
    println(diff1.lessThan(diff2))
}

and any Diff that doesn't implement Diff<SameType> will not be accepted.

Example:

class CDiff(private val value: Int) : Diff<DDiff> { // <-- Wrong type!
    override fun lessThan(other: DDiff) = value < other.value
}

class DDiff(val value: Int) : Diff<DDiff> {
    override fun lessThan(other: DDiff) = value < other.value
}

fun test() {
    diffUser(CDiff(3), CDiff(4)) // <-- Doesn't compile due to the generic constraint
    diffUser(DDiff(3), DDiff(4))
}

This same approach is used by the Comparable class.


Although this works, what you really want is a "self type" and this is not supported, although it was on the roadmap at some point. I believe JetBrains denied the request for this, though I cannot find the bug report.

This answer details a workaround in Java using the CRT pattern, though it is not necessarily type safe.

Related