5

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.

SadStudent
  • 287
  • 1
  • 4
  • 16

5 Answers5

3

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;
    }

}
m0skit0
  • 25,268
  • 11
  • 79
  • 127
  • Thanks! and yes, I'm well aware of the reason it's not working I was just not sure how to solve this without "loosing" the generality:\ the reason I cannot do the second solution is that there's another code which is not mine that instantiates SimpleFinder and I can't break it... – SadStudent Jun 12 '13 at 20:16
  • I could ask for two arguments and send `, T>` like you suggested to the interface but then `S` would be redundant and that's kind of ugly too, isn't it? – SadStudent Jun 12 '13 at 20:29
  • 2
    The unchecked cast option is unsafe - I would recommend removing it from the answer or add least adding a more meaningful disclaimer. – Paul Bellora Jun 12 '13 at 20:51
  • @PaulBellora thanks for pointing that out, but if S extends Stack, why is the conversion unsafe? Polymorphism allows me to cast any children of Stack to Stack. – m0skit0 Jun 12 '13 at 20:57
  • 2
    Basically, an implementation of `Stack`, call it `BlueStack`, isn't required to return a `BlueStack` from `getCopy` - it could return a `RedStack` instead. Even if the idea is to return the "self type", there's no way to enforce it. – Paul Bellora Jun 12 '13 at 21:36
  • @PaulBellora yes we can not expect it to be intelligent enough to "understand" it , so maybe you have an idea about how am I doing it (considering my edit of course :) ) without ugly casts? – SadStudent Jun 12 '13 at 21:53
  • @PaulBellora Thanks but still, how does that matter? I mean, RedStack still extends Stack. The cast is still safe. – m0skit0 Jun 13 '13 at 07:37
  • 2
    @m0skit0 You're not casting to plain `Stack` though, you're casting to `S`, which can't be guaranteed to be correct. And since it's an unchecked cast it won't fail fast when it's wrong. – Paul Bellora Jun 13 '13 at 14:28
2

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.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
1

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;
}
Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • even thought IMHO a redesign is needed unfortunately, as I mentioned in the question, I cannot change the interface only to implement it, so I guess I'm stuck with an ugly cast, but I think your solution is very nice... – SadStudent Jun 13 '13 at 17:25
0
public class SimpleFinder<S extends Stack<T>,T extends Item> implements Finder<S,T>{
    public S find(S s, int a){
        Stack<T> stack = ....;
        ...
        stack = s.getCopy();
        .....
        return (S) stack;
    }
}

should work. Keep in mind that stack must be a Stack<T> and not S to match getCopy() return type. I would expect S type to be Ok, since it extends Stack<T>, but implementing it this is the behavior that I'm observing.

Lucia Pasarin
  • 2,268
  • 1
  • 21
  • 37
  • Nope still doesn't compile: "Return type for the method is missing". That return type is not valid (up to Java 7). – m0skit0 Jun 12 '13 at 19:57
  • Surprisingly, it seems like I came out with your first version of your answer without looking at it! I suppose this is a good sign. I just wanted to let you know it was without looking at yours because I don't like when other people just literally copy my answers, so this is not the case! :) – Lucia Pasarin Jun 12 '13 at 21:01
0

Your type S is a subtype of Stack<T> but the copy method is upcasting it to a Stack<T> that may be any subtype of Stack<T>. You will have to cast the result of copy to S

gma
  • 2,563
  • 15
  • 14