Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: returning a class that implements an interface which has type-inference

first time posting and sorry the title is probably rubbish. I'm trying to use type inference but struggling to make it work properly when using a factory pattern, let me throw down some code to make this a bit clearer:

private class ParentObject { }

private class ChildObject extends ParentObject { }

private interface Template<T extends ParentObject> {
    void doSomething(T object);
}

private class TemplateImpl implements Template<ChildObject> {
    public void doSomething(ChildObject object) { }
}

So I've got some classes that inherit from Template that do something with an object that inherits from ParentObject (I've only posted one of each in this post). Now this problem I'm having is when I try to generate one of these Template classes I keep getting either an "incompatible types" error when I try to do this:

private class Factory {
    public <T extends ParentObject> Template<T> generate() {
        return new TemplateImpl(); // here is the error
    }
}
private class Service {
    public <T extends ParentObject> void magic() {
        Factory f = new Factory();
        Template<T> a = f.generate();
    }
}

Or I get an "unchecked assignment" warning (the code works as intended like this but if I'm doing something wrong I'd rather fix it!) when I do this:

private class AlternativeFactory {
    public Template generate() {
        return new TemplateImpl();
    }
}
private class Service {
    public <T extends ParentObject> void magic() {
        AlternativeFactory af = new AlternativeFactory();
        Template<T> b = af.generate(); // warning here
    }
}

Does anyone have any insight onto how I can make this work without any warnings? I've not used type-inference much so apologies if this is a simple one! What I'm not understanding is why I can't return TemplateImpl as a Template given it implements Template?

Thanks!

EDIT: In fact, the factory I want to implement looks something like this, and it seems this is where the issue around type-inference exists:

private class Factory {
    public Template<T extends ParentObject> generate(int option) {
        switch (option) {
            case 1:
                return new TemplateA(); // implements Template<A> where A extends ParentObject
            case 2:
                return new TemplateB(); // implements Template<B> where B extends ParentObject
            default:
                throw new IllegalArgumentException();
        }
    }
}

EDIT: Decided to go with the code I provided above (AlternativeFactory) and just use SuppressWarnings in the service method that calls the factory since it seems what I hoped to achieve is not possible. I know this uses raw types and is bad practise but I have lots of tests around these Template objects and AlternativeFactory including type checks so this will have to do for now.

like image 729
jurym Avatar asked Jun 06 '19 18:06

jurym


2 Answers

TemplateImpl is only compatible with Template<ChildObject>, so new TemplateImpl() cannot be a valid return value for public <T extends ParentObject> Template<T> generate() - because T can be a different subclass of ParentObject

The easiest change is to either make Template a class:

private class Template<T extends ParentObject> {
    void doSomething(T object){/*implement*/}
}

or, if it has to be an interface, make TemplateImpl generic too:

private class TemplateImpl<T extends ParentObject> implements Template<T> {
    public void doSomething(ChildObject object) { }
}

Which will allow your factory to use the type parameter:

private class Factory {
    public <T extends ParentObject> Template<T> generate() {
        return new TemplateImpl<T>(); //T is not bound to ChildObject
    }
}

If all you need is to make sure that TemplateImpl only works as Template<ChildObject>, then your factory method doesn't need to be generic:

private class TemplateImpl implements Template<ChildObject> {
    public void doSomething(ChildObject object) { }
}

//and the factory:
private class Factory {
    public Template<ChildObject> generate() {
        return new TemplateImpl(); //TemplateImpl is a Template<ChildObject>
    }
}

Your second solution should be avoided because it uses raw types.

like image 70
ernest_k Avatar answered Oct 15 '22 17:10

ernest_k


You error is that TemplateImpl is not generic. It already has a definition. And you generate() method is parametrized. TemplateImpl is forced to use ChildObject, while your generate() says to return "something" extending Parent. You can cast to Template the return statement as a workaround.

like image 28
Perimosh Avatar answered Oct 15 '22 17:10

Perimosh