javaabstractunbounded-wildcard

Java - Calling method from child of abstract class


Given the following abstract class:

public abstract class BaseVersionResponse<T extends BaseVO> {

    public abstract void populate(T versionVO);

}

and the following child class:

public class VersionResponseV1 extends BaseVersionResponse<VersionVOV1>
{
    protected String testFieldOne;
    protected String testFieldTwo;

    public String getTestFieldOne() {
        return testFieldOne;
    }  
    public void setTestFieldOne(String value) {
        this.testFieldOne = value;
    }
    public String getTestFieldTwo() {
        return testFieldTwo;
    }  
    public void setTestFieldTwo(String value) {
        this.testFieldTwo = value;
    }

    @Override
    public void populate(VersionVOV1 versionVO) {

        this.setTestFieldOne(versionVO.getFieldOne());
        this.setTestFieldTwo(versionVO.getFieldTwo());
}

I desire to do something like this from a calling method:

public void getVersionInfo(String version) {

    BaseVO versionVO = null;
    BaseVersionResponse<? extends BaseVO> baseVersionResponse = null;

    baseVersionResponse = createVersionResponse(version);

    versionVO = createVersionVO(version);

    baseVersionResponse.populate(versionVO);

}

where createVersionResponse(...) and createVersionVO(...) look like this:

public BaseVersionResponse<? extends BaseVO> createVersionResponse(String version) {

    BaseVersionResponse<? extends BaseVO> specificVersionResponse = null;

    if (version.equalsIgnoreCase("V1")) {

        specificVersionResponse = new VersionResponseV1();

    } else if (version.equalsIgnoreCase("V2"))

        specificVersionResponse = new VersionResponseV2();

    return specificVersionResponse;
}

public BaseVO createVersionVO(String version) {

    BaseVO versionVO = null;

    if (version.equalsIgnoreCase("V1")) {

        versionVO = new VersionVOV1();

    } else if (version.equalsIgnoreCase("V2"))

        versionVO = new VersionVOV2();

    return versionVO;
}

and VersionVOV1 looks like this:

public class VersionVOV1 extends BaseVO {

    private String fieldOne = null;
    private String fieldTwo = null;
    private String fieldThree = null;

    public String getFieldOne() {
        return fieldOne;
    }
    public void setFieldOne(String fieldOne) {
        this.fieldOne = fieldOne;
    }
    public String getFieldTwo() {
        return fieldTwo;
    }
    public void setFieldTwo(String fieldTwo) {
        this.fieldTwo = fieldTwo;
    }
    public String getFieldThree() {
        return fieldThree;
    }
    public void setFieldThree(String fieldThree) {
        this.fieldThree = fieldThree;
    }

}

My problem arises when I try to compile this line of code:

baseVersionResponse.populate(versionVO);

in getVersionInfo(...). I'm getting a message that looks like this:

The method populate(capture#3-of ?) in the type BaseVersionResponse is not applicable for the arguments (BaseVO)

on the populate method above.

My thought was (which is apparently incorrect) that since the baseVersionResponse is, at this point in the code, actually a specific child instance, that the class would know exactly which populate method to call from that specific child class.

What am I doing wrong here? Is there a better way to do this if this isn't the correct approach?

Thank you for your time!


Solution

  • Ok, I took a better look at this today. The problem is that the wildcard, while the right way to go, precludes you from doing:

    BaseVO versionVO = createVersionVO(version);
    

    Because the populate call wants an extension of BaseVO, not an actual BaseVO, which doesn't qualify. That means you can't pass that versionVO variable directly.

    So, to keep the type checking in place, which I think is good because you'll always want an implementation, leave pretty much everything as-is above, and change your BaseVersionResponse class to something like:

    public abstract class BaseVersionResponse<T extends BaseVO> {
    
        public T getVersion(BaseVO versionVO) {
            try {
                return (T) versionVO;
            } catch (ClassCastException e) {
                throw new IllegalArgumentException();
            }
        }
    
        public abstract void populate(BaseVO versionVO);
    
    }
    

    So, populate method now takes a BaseVO, and there's a new getVersion method to do some explicit casting for us. This should be ok since we know that the factory will always supply the right thing, but if another caller doesn't, an IllegalArgumentException is thrown.

    Now, in your response class implementation, change the populate method accordingly:

    public void populate(BaseVO version) {
        VersionVOV1 versionVO = getVersion(version);
        this.setTestFieldOne(versionVO.getFieldOne());
        this.setTestFieldTwo(versionVO.getFieldTwo());
    }
    

    So, we've changed the populate method to take BaseVO, and the getVersion method does the casting for us. All the other type checks still apply, and we're good to go.

    The casting makes it feel not as clean, but for the factory approach you're using, it's really the only way (I can think of) to keep the guarantees made by the type declarations and the code pattern in tact.

    Hope that helps!