Question

I have this design, and I'm not sure why it doesn't work.

interface BaseType {}

interface TypeA extends BaseType {}

interface TypeB extends BaseType {}

interface Query<T extends BaseType> {
    public String get();
}

interface Result<T extends BaseType> {
    public String get();
}

interface Service<T extends BaseType> {
    public Result<T> get(Query<T> query);
}

class SomeResult implements Result<TypeA> {
    private String s;
    public SomeResult(String s) { this.s = s; }
    public String get() { return this.s; }
}

class SomeQuery implements Query<TypeA> {
    public String get() { return "blah"; }
}

class SomeQuery2 implements Query<TypeA> {
    public String get() { return "blah2"; }
}

class SomeService implements Service<TypeA> {
    /** OK -- but notice the ambiguous parameter type */
    /*
    public SomeResult get(Query<TypeA> query) {
        if (query instanceof SomeQuery) return new SomeResult(query.get());
        else return null;
    }
    */

    /** NOT OK -- but this is the parameter I want to keep; notice SomeQuery IS-A Query<TypeA> */
    public SomeResult get(SomeQuery query) { return new SomeResult(query.get()); };
    /**
     * Main.java:27: error: SomeService is not abstract and does not override abstract method get(Query<TypeA>) in Service
     * class SomeService implements Service<TypeA> {
     * ^
     * 1 error
     */
}

public class Main {
    public static void main(String args[]) {
        SomeQuery someQuery = new SomeQuery();
        SomeQuery2 someQuery2 = new SomeQuery2();
        SomeService someService = new SomeService();
        System.out.println(someService.get(someQuery).get());
    }
}

I'm new to generics, and don't quite understand what contract I'm violating here. I want the service to be tightly bounded, and even if I can bound the return type, I cannot seem to do so for the parameter. Which means, I'll need to do an instanceof check inside the service to make sure I'm getting the right parameter. I want to avoid that. Any ideas?

Was it helpful?

Solution

You're allowed to make an overriding method's return type more specific due to return type covariance, but you can't change the method's parameters without changing its signature. That's why the compiler complains that you haven't implemented get(Query<TypeA>) when you change it to get(SomeQuery). You'll need to make Service more flexible in order to get what you want:

interface Service<T extends BaseType, Q extends Query<T>> {
    public Result<T> get(Q query);
}

class SomeService implements Service<TypeA, SomeQuery> {

    @Override
    public SomeResult get(SomeQuery query) {
        ...
    }
}

Also note that the narrowed return type doesn't matter when coding to interface: when SomeService is typed as Service<TypeA, SomeQuery>, get will still return Result<TypeA>. So you might consider making a similar change for the result type:

interface Service<T extends BaseType, Q extends Query<T>, R extends Result<T>> {
    public R get(Q query);
}

class SomeService implements Service<TypeA, SomeQuery, SomeResult> {

    @Override
    public SomeResult get(SomeQuery query) {
        ...
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top