1

I have this interface:

public interface Inflatable {
    Pump<? extends Inflatable> getPump();
}

and this interface:

public Pump<T extends Inflatable> {
    int readPressure(T thingToInflate);
}

Now this class:

public class Preparer {
    public <T extends Inflatable> void inflate(T thingToInflate) {

        int pressure = thingToInflate.getPump().readPressure(thingToInflate);
    }
}

does not compile, with this error:

The method readPressure(capture#1-of ? extends Inflatable) in the type Pump is not applicable for the arguments (T)

What is wrong here? The variable thingToInflate has to be an instance of a subclass of Inflatable (because of <T extends Inflatable>, right?), and the readPressure method is defined to require a subclass of Inflatable.

I know that this particular example is contrived, but the general case is that given an instance of T, I can't then pass that instance to a method in another class that appears to define T in exactly the same way. Can I fix this?

Dave Mulligan
  • 1,623
  • 3
  • 19
  • 31
  • From the given example, it looks like `Pump` shouldn't actually be generic because the usage you're anticipating suggests `int readPressure(Inflatable)`. Possibly also see [PECS](https://stackoverflow.com/q/2723397/2891664). – Radiodef Aug 10 '17 at 19:03

2 Answers2

1

The Pump returned by the getPump might not be Pump<T>. It returns Pump<U>, where U is something that extends Inflatable. It's not safe to assume that T is a subtype of U.

Let's assume that there're 2 concrete classes that implement Inflatable: C1 and C2. getPump may return an instance of Pump<C1>. Let's assume that T is C2. An object of type C2 is not an instance of C1, so it can't be passed to the readPressure method.

That's why one can't "fix" it without a type safety violation.

Here's a concrete example showing that you're trying to do a wrong thing:

   class C1 implements Inflatable, Pump<C1> {
        @Override
        public Pump<? extends Inflatable> getPump() {
            return this; // an instance of C1 which implements Pump<C1>
        }

        @Override
        public int readPressure(C1 thingToInflate) {
            return 0;
        }
    }

    class C2 implements Inflatable {
        @Override
        public Pump<? extends Inflatable> getPump() {
            return new C1(); // again, an instance of C1 which implements Pump<C1>
        }
    }

    public class Preparer {
        public <T extends Inflatable> void inflate(T thingToInflate) {
            int pressure = thingToInflate.getPump().readPressure(thingToInflate);
            // Let's assume that it were possible. What happens if one calls
            // new Preparer().inflate(new C2())?
            // new C2().getPump() returns an instance of C1 which implements Pump<C1>
            // It's readPressure method expects an instance of C1. But T = C2, so
            // the thingToInflate is not an instance of C1. 
            // If the compiler allowed this to happen, the type safety
            // would be violated. 
        }
    }

The only thing you can do is redesigning your interfaces. I can't tell you an exact way to fix it because I don't know what your code is trying to accomplish in the first place.

kraskevich
  • 18,368
  • 4
  • 33
  • 45
1

When you write a method with a return type of T, that doesn't mean the thing returned can't be a subclass of T. It just means the type is T as far as the compiler is concerned. Just like if the method returns a List, the thing returned is a LinkedList or ArrayList or whatever.

When you specify T extends something for a generic type you're saying that the compile-time type is a range of types, it could be T or it could be any type extending T. And there's no way to tell what the type is without using things like instanceof, and then casting, defeating the purpose of using generics.

Specifying a range of types makes sense for method parameters. If I have a method that takes a list of T extends Animal, the caller can pass in a List of Dog or a List of Cat. In other contexts it's not helpful.

So don't use wild cards with extends/super for return types. There's an entry in Effective Java (chapter 5, item 28) saying this, to quote:

Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.

(The text is bolded as it appears in the book, it is not something I introduced.) Note that this quote is found within the discussion of using bounded wildcards. If your client using a method genuinely doesn't care about what a type is (for instance returning a

Class<?>

from a method) then go ahead.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276