vjk
vjk

Reputation: 2283

Return sub class type from super type method

public abstract class BaseClass<T extends BaseClass<T>> {

    T method1(){
        return getThis();
    }

    public  abstract T getThis();
}

public class SubClass extends BaseClass<SubClass> {
    public SubClass getThis(){
        return this;
    }
}

If it's just one level of inheritance i can do something like the above and get a reference of SubClass when I call method1().

What if I have inheritance at two levels like

public abstract class SubClass1<T extends SubClass1<T>> extends BaseClass<SubClass1<T>> {
    @Override
    public SubClass1<T> getThis() {
        return this;
    }
}

public class SubSubClass1 extends SubClass1<SubSubClass1> {



}

what should I change to the method1 and BaseClass so that when method1 is called I can get back SubSubClass1 type. I wan't to do this without any suppress warnings

Upvotes: 4

Views: 1626

Answers (2)

newacct
newacct

Reputation: 122429

SubSubClass1 must be the one implementing getThis(), like this:

public abstract class BaseClass<T> {
    T method1(){
        return getThis();
    }
    public abstract T getThis();
}

public abstract class SubClass1<T> extends BaseClass<T> {
}

public class SubSubClass1 extends SubClass1<SubSubClass1> {
    @Override
    public SubSubClass1 getThis() {
        return this;
    }
}

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180141

It is important to understand that each type-parameterized class's type parameters are its own. In particular, they (the parameters themselves) are not shared with super- or subclasses. It is common for type-parameterized subclasses to intentionally share type parameter values with their superclasses, but that's a different thing.

Classes get to specify their superclasses, but they do not get to alter their superclass' own inheritance. Thus, if SubClass1 extends BaseClass<SubClass1<T>> then BaseClass's type parameter for class SubClass1<T> and every subclass thereof is SubClass1<T>, because that class alone is the one that SubClass1<T> extends.

Any concrete subclass of your BaseClass<T> must implement its abstract method getThis(), and that requires such a class (e.g. SubClass1) to specify BaseClass's type parameter T as an instantiable type (else it cannot provide a return value). From that point on down the hierarchy, subclasses can employ covariant return types to narrow the type returned by getThis(), but they cannot change the BaseClass type parameter specified by SubClass1, except to the extent that it depends on SubClass1's own type parameter(s). For example, this compiles cleanly:

public abstract class BaseClass<T extends BaseClass<T>> {
    // ...
    public  abstract T getThis();
}

public abstract class SubClass1<T extends SubClass1<T>> extends BaseClass<SubClass1<T>> {
    @Override
    public SubClass1<T> getThis() {
        return this;
    }
}

public class SubSubClass1 extends SubClass1<SubSubClass1> {
    @Override
    public SubSubClass1 getThis() {
        return this;
    }
}

But that's an exercise of covariant return types, not type parameterization, so this also works:

public abstract class BaseClass {
    // ...
    public abstract BaseClass getThis();
}

public abstract class SubClass1 extends BaseClass {
    @Override
    public SubClass1 getThis() {
        return this;
    }
}

public class SubSubClass1 extends SubClass1 {
    @Override
    public SubSubClass1 getThis() {
        return this;
    }
}

Props to @HannoBinder who first suggested covariant return types in the comments.

Note that if the main idea here was use a type parameter to mark which descendant of BaseClass you actually have -- or at least an upper bound on that -- then that's wrongheaded. In that approach, the type BaseClass<SomeSubClass> is no more meaningful or expressive than the type SomeSubClass itself. The parameterized version is distinguished only by being harder to work with.

Upvotes: 2

Related Questions