Home > Net >  Java: is there a way for function to allow all child class as return type using generics?
Java: is there a way for function to allow all child class as return type using generics?

Time:09-17

Coming across a problem that I could not make a function to allow a generic return type for all the child class of a parent class.

Is there a way to create a function that allows me to return any of these children class type base on an argument?

I have the following parent class:

abstract class Number {
    int res = 1;
    abstract static class Builder<T extends Builder<T>> {
        int res = 100;

        public T setNum(int num) {
            this.res = num;
            return self();
        }
        abstract Number build();

        abstract T self();
    }

    Number(Builder<?> builder) {
        res = builder.res;
    }
}

and some children class:

class One extends Number{
    private int size = 1;
    static class Builder extends Number.Builder<Builder> {
        private int size = -1;
        public Builder setSize(int size) {
            this.size = size;
            return self();
        }

        @Override
        public One build() {
            return new One(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private One(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

class Two extends Number {
    private String size = String.valueOf(1);
    static class Builder extends Number.Builder<Builder> {
        private String size;
        public Builder setSize(String size) {
            this.size = size;
            return self();
        }

        @Override
        public Two build() {
            return new Two(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private Two(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

Note the parent class and child classes are not done yet, but it is going to have a similar format with just more fields so this would still apply

This is something that I want to achieve:

public <T> T loadNumber(String id) {
    if (id.equals('1')) {
        return new ONE.Builder.build(); // this will report error right now
    }
    elif (id.equals('2')) {
        return new TWO.Builder.build(); // this will report error right now
    }
    return null;
}

CodePudding user response:

You should be able to return any child class by simply using the parent class as a return type, in your case:

public Number loadNumber(String id) {
// your code implementation
}

Now, to save this into a variable you can use the same logic and declare the variable as the parent type. After the method returns, the variable will be casted into the whatever child class instance it returned, for example in your case:

Number obj = loadNumber("1");

The obj object will cast to an instance of One class. You can test this by printing out the object class after the above line:

System.out.println(obj.getClass());

This should print out class One or the name of whatever child class you saved into the variable.

CodePudding user response:

The problem is that type arguments are specified by the caller. Your method

public <T> T loadNumber(String id) {

can be called like

Two two = loadNumber("One");

causing the compiler to infer

Two two = loadNumber<Two>("One");

which is why the compiler expects your method implementation to conjure a T out of thin air, and is not satisfied with a One. After all, T could stand for Two, as in the example above, and One isn't Two.

Put differently, the method signature

public <T> T loadNumber(String id) {

doesn't make sense, because the method must return a T, but has no way to determine, at runtime, what T is (due to type erasure, methods can't reflect on their type parameters).

So you have two choices:

Either you return the base type

public Number loadNumber(String id) {

and have the caller, who presumably knows which type the id he passed corresponds to, cast it to that type,

or you use an ID that whose type is itself generic and describes the type it maps to:

interface Builder<P extends Number, B extends Builder<P>> {
    P build();
    B self();
}

interface ID<P extends Number> {
    Builder<P, ?> builder();

    static ID<One> one = One.Builder::new;
    static ID<Two> two = Two.Builder::new;
}

and then you can declare

public <N extends Number> N loadNumber(ID<N> id) {
    return id.builder().build();
}

and use it like

One one = loadNumber(ID.one);
Two two = loadNumber(ID.two);

but not

One one = loadNumber(ID.two); // compilation error
  • Related