Batt84
Batt84

Reputation: 55

Kotlin: Override (and specify) generic interface functions

I want use generic interfaces that will be specified by their implementing classes, and all in all this has been working fine, like:

interface Remove <E> { fun remove(entity: E) }

class MyHandler : Remove <MyClass> {
   override fun remove(entity: MyClass) { */do stuff/* }
}

However, I have a case (so far, expecting more to come) where I want the function itself to be generic. Writing the interface is no problem at all:

interface FindByID <E> { fun <K : Serializable> findByID(id: K): E }

I need K to be serializable because that's a requirement of some function I need to call.

The compiler doesn't seem to agree with my implementation attempt. When I do this:

override fun <String> findByID(id: String): User {
    return someFunction(User::class.java, id) as User
}

I get two compiler errors:

  1. overrides nothing
  2. id is not Serializable

However, when I remove override and <String> from the signature, it works fine. This means String is serializable, which my research shows as well.

What seems to be the problem here?

Also, yes, I know that I could work around this issue in a couple of ways, like

Although open to suggestions, I'm less interested in workarounds, but would rather like to understand and (if possible) solve the actual problem or at least know it can't be done so I can take that into account for planning

Upvotes: 3

Views: 4008

Answers (1)

Joffrey
Joffrey

Reputation: 37660

The current problem

With your current declaration of <String>, you're not specializing the type parameter as you might think. What you're actually doing is declaring a type parameter that happens to be named String (nothing to do with the well-known String type, just an unfortunate name collision). With syntax coloring, you should see that String here is in the color of a type parameter, not the same color as the String type would be. Change this name to anything else, and you'll realize the confusion.

So if we rename this to K, the problems become obvious: problem 1 "overrides nothing" is because your generic type parameter doesn't have the same : Serializable constraint as the findByID method defined in the interface. And problem 2 stems from it.

Solutions

Also, yes, I know that I could work around this issue in a couple of ways, like

  • not specifying K on override, but on call (myHandler.findByID("myID"))

This point is actually the essence of the issue, not a workaround: defining a generic function in your interface actually makes it part of the contract for this function to be generic. Implementations must have a generic type parameter here.

What to do to fix it depends on what you expect to happen.

If you're ok with having generic functions in your implementations, then leave the interface as you declared it, but accept that the implementations have to deal with all possible values of K, which is most likely not what you want here.

If you want to define a specific type in your implementations, K should be part of the interface definition (not the interface method), and the method should not have a type parameter itself, but simply accept the existing K from the interface:

interface FindByID<E, K : Serializable> {
  fun findByID(id: K): E
}

Upvotes: 5

Related Questions