android developer android developer - 3 months ago 12
Java Question

How to Correctly return something that extends from a class and implements an interface?

Background



This question is for both Java and Android developers.

On Android, I want to have a function that returns something that extends from View and implements Checkable, both are part of the Android API.

What I've tried



After searching a bit here and on other places on the Internet, I've found this solution:

private <T extends View & Checkable> T get() {
return (T) mView;
}


Note that mView is of any type that extends View (and there are plenty) but I guarantee that it implements Checkable since only those types get to be there.

This shows a warning of "Type safety: Unchecked cast from View to T" , but that's not the problem.
The problem is that I can't use this method correctly, for example:

View v=get(); //this works fine
Checkable c=get(); // this shows error


the second line shows the error:


Bound mismatch: The generic method get() of type ... is not
applicable for the arguments (). The inferred type Checkable&View is
not a valid substitute for the bounded parameter


So I've tried using "," instead of "&" . same error.

However, when I use "," and reverse the order , I get a warning "The type parameter View is hiding the type View" , and both lines work fine.

This is the code:

private <T extends Checkable, View> T get() {
return null;
}

private void foo() {
final View v = get(); // this works fine
final Checkable c = get();// this works fine too
}


However, when I send the returned type to other functions, they don't get the type itself as something that extends the class and the interface, and need casting all the time (to View).

The question



What is the correct way to do it?

If the last way I've shown is the correct one, why do I get a warning about it? and why is the order important if there is always a max of one class to extend ?

Answer

You get the error, because the type inferred for the parameter T at the second call site, i.e. Checkable c = get(), is simply Checkable, as the error message correctly reports.

If you force the call to use a compatible type View & Checkable, it will compile. To do that, you must supply the type explicitly, e.g.:

private <T extends View & Checkable> T get() {
   return (T) mView;
}

private <T extends View & Checkable> void useGeneric() {
    View v = this.get(); // no need for an explicit type
    Checkable c = this.<T>get(); // an explicit type needed
    T t = this.get(); // best way
}

private void useSpecific() {
    class Specific extends View implements Checkable {}
    Checkable c = this.<Specific>get(); // risk of ClassCastException
    Specific s = this.get(); // best way; risk of ClassCastException
}

The fact that in case of assigning to a class it doesn't require an explicit type, while it requires one in the interface case, looks to me like a type inferrence algorithm artifact, a class / interface asymetry.

Comments