Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign a subclass of a Generic class to a super class of this class

Tags:

java

generics

I have couple of supplied interfaces

public interface Finder<S extends Stack<T>,T extends Item> {
    public S find(S s, int a);
}

public interface Stack<T extends Item> {
    Stack<T> getCopy();
}

and a class that implements the first:

public class SimpleFinder<S extends Stack<T>,T extends Item> implements Finder<S,T>{

    public S find(S s, int a){
         S stack = ....;
         ...
         stack = s.getCopy(); \\Error: incompatible types
                              \\       required: S
                              \\       found:    Stack<T>
        .....
        return stack;
    }


}

If I cannot change any interface what would be the best course of action while keeping the implementation as generic as possible?

EDIT Some other code which I cannot break instantiates SimpleFinder<ClassA,ClassB> so I should have two generic types in the implementation as well.

like image 653
SadStudent Avatar asked Jun 12 '13 19:06

SadStudent


3 Answers

The problem is that obviously Stack<T> is not S extends Stack<T>. Java is strongly typed and won't let you do such things.

You can either cast to Stack<T>, in which case you will still get a warning about unchecked conversion. This means this conversion is unsafe.

public class SimpleFinder<S extends Stack<T>, T extends Item> implements Finder<S, T> {

    @Override
    public S find(S s, int a) {
        Stack<T> stack = s.getCopy();
        return (S) stack;
    }

}

or simply use Stack<T> instead of S extends Stack<T>, which is my recommendation:

public class SimpleFinder<T extends Item> implements Finder<Stack<T>, T> {

    @Override
    public Stack<T> find(Stack<T> s, int a) {
        Stack<T> stack = s.getCopy();
        return stack;
    }

}
like image 162
m0skit0 Avatar answered Oct 04 '22 05:10

m0skit0


Since you can't change the interfaces, you have no choice but to do brute cast.

In a more general discussion, what we need here is "self type", we want to say that a method invocation foo.bar() should return the static type of foo. Usually self type is wanted for fluent API where the method should return foo itself. In your case you want to return a new object.

In java there's no satisfactory answer for self type. One trick is through self referenced type paramter like Foo<T extends Foo<T>>, however it is very ugly, and it cannot really enforce that any subtype Bar must be a Foo<Bar>. And the trick won't help in your case at all.

Another trick may work

public interface Stack<T extends Item> {
    <X extends Stack<T>> X getCopy();
}

here, the caller supplies the exact return type.

     S stack = ....;
     ...
     stack = s.getCopy();
     // compiles, because X is inferred to be S

This trick helps to simplify call sites. However brute casts still exists, hidden in implementations of getCopy(). This trick is dangerous and caller must know what it is doing. Personally I wouldn't do it; it's better for force caller to do the cast.

like image 25
ZhongYu Avatar answered Oct 04 '22 05:10

ZhongYu


As discussed in the comments, your design necessitates that the getCopy method return the "self type" - that is, a BlueStack<T> implementation would be expected to return a BlueStack<T> from its getCopy, and RedStack<T> should return a RedStack<T> etc.

Unfortunately, there is no way to express the "self type" in Java. As zhong.j.yu points out, a recursive type parameter comes close, for example:

//not recommended!
public interface Stack<S extends Stack<S, T>, T extends Item> {
    S getCopy();
}

But as zhong.j.yu mentions this is unintuitive and would still fail to prevent a BlueStack<T> from "lying" and returning a RedStack<T> from its getCopy.

Instead, I recommend a redesign. Try decoupling the responsibility of copying stacks from the Stack type itself. For example:

public interface StackCopier<S extends Stack<T>, T extends Item> {

    S copy(S original);
}

If implementations of StackCopier need access to private members of their respective Stacks, consider making them nested classes, for example:

class BlueStack<T extends Item> implements Stack<T> {

    ...

    static class Copier<T extends Item> implements StackCopier<BlueStack<T>, T> {

        @Override
        public BlueStack<T> copy(BlueStack<T> original) {

            ...
        }
    }

Of course SimpleFinder would need to be changed to either have a StackCopier<S, T> field or take one as a new parameter of find:

private final StackCopier<S, T> copier = ...;

public S find(S stack, int a) {

     S stackCopy = copier.copy(stack);

     ...

     return stackCopy;
}
like image 31
Paul Bellora Avatar answered Oct 04 '22 06:10

Paul Bellora