5

To an interface, I want to add a function that returns an object of the concrete implementation.

So with the interface:

interface Content {
        fun <???> cloneMe(): ?
}

and the classes

class Music: Content
class Video: Content

the function cloneMe() of the Music class should return a Music object and the the function cloneMe() of the Video class should return a Video object.

The closest I've come is:

interface Content {
    fun <T: Content> cloneMe(): T
}

class Music : Content {
    override fun <T : Content> cloneMe(): T {
        return Music() as T
    }
}

then I can do

val music: Music = Music().cloneMe<Music>()

The problem here is that I have to do an unchecked cast, which 'allows me' (i.e. will compile) to do

class Music : Content {
        override fun <T : Content> cloneMe(): T {
            return Video() as T
        }
}

which is problematic.

What's the best way to achieve this?

fweigl
  • 21,278
  • 20
  • 114
  • 205

2 Answers2

16

Typically this is done by parameterizing the Content interface as well, as follows:

interface Content<T : Content<T>> {
    fun cloneMe(): T
}

And the implementation:

class Music : Content<Music> {
    override fun cloneMe(): Music {
        return Music()
    }
}
hotkey
  • 140,743
  • 39
  • 371
  • 326
3

What you're trying to achieve is not necessary related to any Generics. Instead you could just use the type directly and implement a more detailed override.

interface Content {
    fun cloneMe(): Content
}

class Music : Content {
    override fun cloneMe() = Music()
}
tynn
  • 38,113
  • 8
  • 108
  • 143
  • This offloads the responsibility for downcasting onto the caller, the avoidance of which it seems to me is the implied intent of the original question. – Tim Keating Jun 15 '18 at 16:43
  • @TimKeating how so? The return type of `Music.cloneMe()` is actually `Music`. The only advantage of having Generics here is the checking of the proper return type if the class is defined properly. – tynn Jun 16 '18 at 00:40
  • Not true. The return type of `cloneMe()` is `Content`, regardless of the fact that you call it from a `Music` and it returns a `Music` instance. You can't, for example, call a hypothetical `setVolume()` method on your cloned object without explicitly casting it to a `Music`. You can easily verify this in the REPL. – Tim Keating Jul 03 '18 at 01:22
  • 3
    @TimKeating it depends on the type of your variable. If you call `cloneMe()` on a `Content` instance, you'll only get a `Content` returned. The same holds true for `Content<*>`. If you have a `Content` or `Music` variable, you'll get a `Music` returned. For both cases you need to know about the `Music` definition. Using the generic supertype won't yield anything. – tynn Jul 03 '18 at 06:59
  • This won't work if you're using the `Content` interface as a generic type parameter in other functions. – Adam Arold Sep 02 '20 at 19:11