covariant return type for default method

388 Views Asked by At

I can override a method with covariant return type, but is it possible to override a default method with covariant return type? In the following example, I would like to override getFirstLeg without rewriting the default method, but Java does not allow that. I also do not want to make Animal a generic interface because there may be many parameters and Animal is used in a lot of places.

interface Leg {
}

interface Animal {
    List<? extends Leg> getLegs();

    default Leg getFirstLeg() {
        return getLegs().get(0);
    }
}

abstract class AnimalImpl<T extends Leg> implements Animal {
    private List<T> legs;

    @Override
    public List<T> getLegs() {
        return legs;
    }
}

interface DuckLeg extends Leg {
}

interface Duck extends Animal {
    @Override
    List<? extends DuckLeg> getLegs(); //covariant return type

    @Override
    DuckLeg getFirstLeg(); //I do not want to rewrite this method
}

class DuckImpl extends AnimalImpl<DuckLeg> implements Duck {
    //Error: java: DuckImpl is not abstract and does not override abstract method getFirstLeg() in Duck
}

Update: The following code compiles, but the new problem is that Duck is no longer an Animal.

interface Leg {
}

interface AnimalGeneric<T extends Leg> {
    List<? extends T> getLegs();

    default T getFirstLeg() {
        return getLegs().get(0);
    }
}

abstract class AnimalImpl<T extends Leg> implements AnimalGeneric<T> {
    private List<T> legs;

    @Override
    public List<T> getLegs() {
        return legs;
    }
}

interface Animal extends AnimalGeneric<Leg> {
    //empty
}

interface BirdLeg extends Leg {
}

interface BirdGeneric<T extends BirdLeg> extends AnimalGeneric<T> {
}

class BirdImpl<T extends BirdLeg> extends AnimalImpl<T> implements BirdGeneric<T> {
}

interface Bird extends BirdGeneric<BirdLeg> {
    //empty
}

interface DuckLeg extends BirdLeg {
}

class DuckImpl extends BirdImpl<DuckLeg> implements Duck {
}

interface Duck extends BirdGeneric<DuckLeg> {
}
2

There are 2 best solutions below

4
rgettman On

Yes, Java does have covariant return types for inherited methods. But the problem here isn't the fact that getFirstLeg is a default method, it's that Java's generics are not covariant; they're invariant. I.e. A List<DuckLeg> is not a List<? extends Leg>, even if a DuckLeg is a Leg.

But you can get around this by making the Animal interface generic, so that the type parameter can change based on the sub-interface(s).

interface Animal<T extends Leg> {
    List<T> getLegs();

    default T getFirstLeg() {
        return getLegs().get(0);
    }
}

Then Duck has a type parameter also, but does not need to override anything anymore. The type argument DuckLeg is assigned to T.

interface Duck extends Animal<DuckLeg> {
    // able to remove the override of getLegs
    //@Override
    //List<DuckLeg> getLegs(); //covariant return type

    // able to remove the override of getFirstLeg
    //@Override
    //DuckLeg getFirstLeg(); //I do not want to rewrite this method
}

Then your DuckImpl class will inherit everything properly, and you won't get the "not abstract and does not override abstract method" error.

Of course with an empty declaration of Duck, you may not need it at all. Just have DuckImpl implement Animal<DuckLeg>.

class DuckImpl extends AnimalImpl<DuckLeg> implements Animal<DuckLeg> {

}
1
Bohemian On

If you want covariance, type the Leg. You can then delete most of your code.

This compiles:

interface Leg {
}

interface Animal<T extends Leg> {
    List<T> getLegs();

    default T getFirstLeg() {
        return getLegs().get(0);
    }
}

abstract class AnimalImpl<T extends Leg> implements Animal<T> {
    private List<T> legs;

    @Override
    public List<T> getLegs() {
        return legs;
    }
}

interface DuckLeg extends Leg {
}

interface Duck extends Animal<DuckLeg> {
}

class DuckImpl extends AnimalImpl<DuckLeg> implements Duck {
    // no errors
}