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

3

8

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 ?

Stortz answered 30/10, 2013 at 9:9 Comment(2)
Is mView a type you created?Fortify
@KevinBowersox this is not the issue. i could even return null there if i wanted to .Stortz
V
4

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.

Vinaya answered 24/8, 2016 at 14:6 Comment(3)
Nice. Didn't know I can do this. What if there is more than 1 interface to return?Stortz
@androiddeveloper: You mean e.g. View & Checkable & Closeable? Just join this way all required interfaces by & at each type parameter definition.Vinaya
OK. Thank you againStortz
M
6

Before I dive into generics.... Why don't you just introduce a CheckableView and return that type?

public class CheckableView extends View implements Checkable {
}


 private CheckableView getCheckableView() {
     return checkableView;
 }

If View would be an interface it works:

interface View {}
interface Checkable {}
class CheckableView implements Checkable, View {}

public class Main {

    private static CheckableView checkableView;

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

    public static void main(String[] args) {
        View view = Main.get();
        Checkable checkable = Main.get();
    }
}

I think the problem in understanding generics is that T extends View & Checkable in the actual case does not mean that you can return any type that extends View and implements Checkable.

It means that the client can cast the returned type to any type that extends View and implements Checkable.

A client could do something like this:

class CheckableView extends View implements Checkable {}
class OtherView extends View implements Checkable {}

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

OtherView otherView = Main.get();
CheckableView checkableView = Main.get();

but what do you want to do now with the implementation?

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

What should this method return? If you return a CheckableView the compiler will say that you must cast it to T, because a client might cast it to any type T. But this also means that you will get an Unchecked cast from CheckableView to T warning, because if the client casts it to any other type than CheckableView, e.g. OhterView, you will get a ClassCastException.

Since the get method implementor can not know to which type the client will ever cast it, he can not determine which object to return.

And if the the implementor returns a specific object (e.g. CheckableView), than the client can only cast it to that type. So why does the implementor not change the method signature to return exactly that type? .... or introduce a special type or interface for this case?

Maybe this is a solution for you

Create an interface that the get method will return, e.g.

public interface CheckableView {
}

Change the get method to return an CheckableView

private CheckableView get() {
}

Write an adapter that adapts your Views that implement Checkable to the CheckableView

public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {

    private T checkableView;

    public  CheckableViewAdapter(T checkableView) {
        this.checkableView = checkableView;
    }
}

Now you can create instances of a CheckableView for every View that implements Checkable

class SomeView extends View implements Checkable {}

SomeView someView = ...;
CheckableView checkableView = new CheckableViewAdapter<SomeView>(someView);

Add the methods that a client of the get method needs to the CheckableView interface and implement it in the CheckableViewAdapter.

public interface CheckableView {
     public void bringToFront();
}

public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {

    private T checkableView;

    public  CheckableViewAdapter(T checkableView) {
        this.checkableView = checkableView;
    }

        public void bringToFront(){
            checkableView.bringToFront();
        }
}

or just

public interface CheckableView {
     public View getView();
     public Checkable getCheckable();
}
Morten answered 30/10, 2013 at 9:15 Comment(14)
What if checkableView is not CheckableView? For example: CheckBox.Eure
this is problematic, since I don't think I can cast the mView to a new class. the mView can be any class instance, yet I guarantee that it extends from View and implements Checkable . also, about View being an interface, this is problematic since it's a part of the API of Android.Stortz
View is android.view.View, it does not help to create your own.Eure
@Eure I know that View in android is a class and not an interface. That's why I said "if it would be"Temperamental
@RenéLink I don't understand what you suggest to do then.Stortz
@androiddeveloper I want to say that the type is bound when the client invokes the get method and this means that the client defines the concrete type then. So you can't return any objects that <T extends View & Checkable>, because a client can cast (bind the type) to any object that fullfils that.Temperamental
@androiddeveloper I mean... you read the method like this "get returns any type that extends View and implements Checkable", but you must read it "get returns a type T that extends View and implements checkable. Type T is bound when the method gets invoked"Temperamental
@RenéLink so what should be done in order to make it work? i want it to return an object that extends a class and implements an interface. it's not possible without any weird things or casting?Stortz
@androiddeveloper I recommend to introduce a special class for that, like the CheckableView that I proposed. Then extends the Views you want from that class. I know that in android you have the problem that you must extend a View or Activity and that makes your implementation hard. So if you want to make a base class and you use a framework that requires a base class than you must extend the frameworks base classes. That design problem in android is the reason why classes like RoboOrmLiteListActivity exists. Maybe you introduce now a RoboOrmLiteListCheckableActivity ;)Temperamental
@RenéLink this will require me to take the code of every View that extends from Checkable , copy it and make a new one that simply extends from the new class. it's not practical.Stortz
@androiddeveloper I don't know how the CheckableView is used in your application, but I will update my answer and try to show another way.Temperamental
@RenéLink this is a nice solution, but it's not what I asked for. the returned value is actually a wrapper/holder that holds what i wanted to returnStortz
@androiddeveloper Yes I know. I will keep watching this question, because I'm also interessted in other solutions.Temperamental
+1 for "Why don't you just introduce a CheckableView and return that type?" - I had a similar issue, and this gave me the hint I needed (should have seen that, facepalm to self...)Ruderal
V
4

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.

Vinaya answered 24/8, 2016 at 14:6 Comment(3)
Nice. Didn't know I can do this. What if there is more than 1 interface to return?Stortz
@androiddeveloper: You mean e.g. View & Checkable & Closeable? Just join this way all required interfaces by & at each type parameter definition.Vinaya
OK. Thank you againStortz
G
0

The problem is you have a comma instead of an ampersand! This definition:

private <T extends Checkable, View> T get() {

Is not an intersection! Rather, it declares 2 generic types, one named T and another named View, which is not used in your class - it's the same as coding:

private <T extends Checkable, V> T get() {

So, change it to:

private <T extends View & Checkable> T get() {

Note with generic intersections, the class must come before the interface.

Guesswarp answered 30/10, 2013 at 9:26 Comment(2)
Read the first part of his question.Eure
I've already tried the "&" way. it didn't work. maybe I missed something?Stortz

© 2022 - 2024 — McMap. All rights reserved.