Overriding the generic return type from an 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 the compiler works with this method declaration:

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

although these are 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 will be that the parameter will have something indicating that it is an endpoint that deals with a particular type of Fetchable. So, why does the compiler require only a direct Endpoint , even if the interface method requires Endpoint<T> ?

+8
java override generics interface
source share
2 answers

An interface method declaration requires a method that can deal with any type of EndPoint :

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

But in implementations of your method, you shorten it to a more specific EndPoint type.

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

Therefore, these are invalid interface implementations. They do not cover all cases required by the interface.

You might want to declare a generic Fetcher , and then do the following:

 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 want the T in Endpoint<T> to be the same as the T returned by this method, you can save the declaration of the interface method as is and use the same declaration in your own 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; } } 
0
source share

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> .

0
source share

All Articles