Reputation: 55
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:
id
is not SerializableHowever, 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
Serializable
instead of <K : Serializable>
K
on override, but on call (myHandler.findByID<String>("myID")
)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
Reputation: 37660
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.
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