Search code examples
javagenericstype-erasure

Avoiding compile errors with generics


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?


Solution

  • 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.