In short, the first one works due to raw types, while the next two fail due to conflicts in the method signature after erasure.
For a more detailed explanation, remember that your Fetcher::fetch method is <T extends Fetchable> T fetch(Endpoint<T>) , so Fetcher developers should implement this method. A good way to think about it is the Liskov signature principle , which basically says: "If your static type is SuperClass, then it doesn't matter which SubClass you have, they should all work, like SuperClass says they do."
See how your second two ads do with this, imagining that someone has a Fetcher and calls it as such:
Endpoint<IntFetchable> intEndpoint = whatever(); IntFetchable i = fetcher.fetch(intEndpoint); // T is inferred to be IntFetchable
As you can see, for this, the fetch method cannot accept EndpointImpl or Endpoint<FetchableImpl> - it really needs to take Endpoint<T> .
You can also ignore the generic type in your method signature and have an override from the raw type (i.e. the type of the erased type). This is what you did with the first of your overrides ( FetchableImpl fetch(Endpoint) ), but raw types lose type safety and have several other errors around them, so I would not recommend it.
If you want fetchers to be specialized for each type of endpoint, you must accept the generic declaration and place it in the Fetcher interface:
public interface Fetcher<T> { T fetch(Endpoint<T> endpoint); }
Now you can have FetcherImpl implements Fetcher<EndpointImpl> .
yshavit
source share