Indigenuity
Indigenuity

Reputation: 9740

Overriding generic return type from interface

I have several interfaces:

public interface Endpoint<T extends Fetchable> {    
    public Class<T> getFetchableType();
}

public interface Fetchable {
    ... fetched data fields
}

public interface Fetcher {
    public <T extends Fetchable> T fetch(Endpoint<T> endpoint);
}

For a class that implements Fetcher, why does the compiler work with this method declaration:

public FetchableImpl fetch(Endpoint endpoint) { return null;}

while these are both incorrect declarations:

public FetchableImpl fetch(EndpointImpl endpoint) { return null;}
--or--
public FetchableImpl fetch(Endpoint<FetchableImpl> endpoint) { return null;}

where EndpointImpl implements Endpoint<FetchableImpl>.

My intuition would be that the parameter would have something designating that it is an endpoint that deals with the specific type of Fetchable. So why does the compiler require just a straight Endpoint, even when the interface method requires Endpoint<T>?

Upvotes: 8

Views: 1597

Answers (2)

Robby Cornelissen
Robby Cornelissen

Reputation: 97120

Your interface method declaration requires a method that is able to deal with any type of EndPoint:

public <T extends Fetchable> T fetch(Endpoint<T> endpoint);

But in your method implementations you narrow it down to a more specific type of EndPoint.

public FetchableImpl fetch(EndpointImpl endpoint) { return null;}
public FetchableImpl fetch(Endpoint<FetchableImpl> endpoint) { return null;}

Hence, these are not valid implementations of the interface method. They don't cover all the cases that the interface requires.

You might want to declare a generic Fetcher, and then implement that:

public interface Fetcher<T extends Fetchable> {
    T fetch(Endpoint<T> endpoint);
}

public class FetcherImpl implements Fetcher<FetchableImpl> {
    public FetchableImpl fetch(Endpoint<FetchableImpl> endpoint) {
        return null;
    }
}

Alternatively, if you just want the T in Endpoint<T> to be the same as the T that is returned by the method, you can keep your interface method declaration as is, and use the same declaration in your implementing class:

public interface Fetcher {
    <T extends Fetchable> T fetch(Endpoint<T> endpoint);
}

public class FetcherImpl implements Fetcher {
    public <T extends Fetchable> T fetch(Endpoint<T> endpoint) {
        return null;
    }
}

Upvotes: 0

yshavit
yshavit

Reputation: 43391

In short, the first works because of raw types, whereas the next two fail because of conflicts in the method signature after erasure.

For the longer explanation, remember that your Fetcher::fetch method is <T extends Fetchable> T fetch(Endpoint<T>), and so implementers of Fetcher must implement that method. A good way of thinking of this is the Liskov substitution principle, which basically says "if your static type is of SuperClass, then it shouldn't matter which SubClass you have, they should all just work as SuperClass says they do."

Let's see how your second two declarations do with that by imagining that someone has a Fetcher and invokes it as such:

Endpoint<IntFetchable> intEndpoint = whatever();
IntFetchable i = fetcher.fetch(intEndpoint); // T is inferred to be IntFetchable

As you can see, for that to work, the fetch method can't take EndpointImpl or Endpoint<FetchableImpl> -- it really needs to take an Endpoint<T>.

You could also disregard the generic altogether in your method signature, and have the override be of the raw type (that is, of the type-erased type). That's what you did with the first of your overrides (FetchableImpl fetch(Endpoint)), but raw types lose type safety and have a few other gotchas around them, so I wouldn't recommend it.

If you want fetchers to be specialized for each kind of endpoint, you should take the generic declaration and put it on the Fetcher interface:

public interface Fetcher<T> {
    T fetch(Endpoint<T> endpoint);
}

Now you can have a FetcherImpl implements Fetcher<EndpointImpl>.

Upvotes: 0

Related Questions