Search code examples
javagenericstype-parameter

How to bind value of type parameter to type of `this`?


interface A<T extends B</*?*/>> {
    method(T param);
}

interface B<U extends A> {
    // ...
}

In code snippet above how can it be expressed that method A#method should only accept parameters that are objects parametrized by type (or its descendants) on which the method is called.

I'd like to achive something like this:

interface Vehicle<T extends SteeringDevice</*?*/> {
    default steer(T steeringDevice) {
        // ...
    }
}

interface SteeringDevice<U extends Vehicle> {
    // ...
}

// -----

class Car implements Vehicle<SeeringWheel> {
    // ...
}

class SteeringWheel implements SteeringDevice<Car> {
    // ...
}

// -----

class Bike implements Vehicle<Handlebars> {
    // ...
}

class Handlebars implements SteeringDevice<Bike> {
    // ...
}

... where it's possible so safely call new Car().steer(new SteeringWheel()) but not new Car().steer(new Handlebars()).


Solution

  • I think this does what you want:

    interface Vehicle<T extends SteeringDevice<? extends Vehicle<T>>> {
        default void steer(T steeringDevice) {}
    }
    
    interface SteeringDevice<U extends Vehicle<? extends SteeringDevice<U>>> {
        // ...
    }
    

    This forces the two compatible implementations to cross-reference each other. Changing the argument to an incompatible type will now trigger a compile error in the paired class.

    One potential issue with the above is that it allows for multiple implementations of SteeringDevice<Car>. You can make the pairing more explicit by adding a self type parameter:

    interface Vehicle<U extends Vehicle<U, T>, T extends SteeringDevice<T, U>> {
        default void steer(T steeringDevice) {}
    }
    
    interface SteeringDevice<T extends SteeringDevice<T, U>, U extends Vehicle<U, T>> {
        // ...
    }
    
    class Car implements Vehicle<Car, SteeringWheel> {
        // ...
    }
    
    class SteeringWheel implements SteeringDevice<SteeringWheel, Car> {
        // ...
    }
    

    It's still possible to create another implementation of SteeringDevice<SteeringWheel, Car>, because Java doesn't have a true self type, but at least it makes the violation more obvious. It also has the advantage of breaking both classes on an invalid type argument.