I have a class called Point
, with a method neighbors()
that returns an array of Point
s:
public class Point {
public Point[] neighbors() { /* implementation not shown */ }
}
I have a subclass of Point
, called SpecialPoint
that overrides neighbors()
to return an array of SpecialPoint
s instead of Point
s. I think this is called covariant return types.
public class SpecialPoint extends Point {
public SpecialPoint[] neighbors() { /* implementation not shown */ }
}
In a separate class, I want to make use of Point
and SpecialPoint
with generics
public <P extends Point> P doStuff(P point) {
P[] neighbors = point.neighbors();
// more stuff here including return
}
This will not compile, because the compiler can only guarantee that P
is some subclass of Point
, but there is no guarantee that every subclass of Point
will override neighbors()
to return an array of itself as I happen to have done with SpecialPoint
, so Java only knows that P#neighbors()
returns Point[]
, not P[]
.
How do I guarantee that each subclass overrides neighbors()
with a covariant return type so I can use it with generics?
Covariant return type refers to return type of an overriding method. It allows to narrow down return type of an overridden method without any need to cast the type or check the return type. Covariant return type works only for non-primitive return types.
How is Covariant return types implemented? Java doesn't allow the return type-based overloading, but JVM always allows return type-based overloading. JVM uses the full signature of a method for lookup/resolution. Full signature means it includes return type in addition to argument types.
It helps to avoid confusing type casts present in the class hierarchy and thus making the code readable, usable and maintainable. We get the liberty to have more specific return types when overriding methods. Help in preventing run-time ClassCastExceptions on returns.
(Yes, this is legal code; see Java Generics: Generic type defined as return type only.) The return type will be inferred from the caller.
You may use an interface:
public interface Point<P extends Point<P>> {
P[] neighbors();
}
public class SimplePoint implements Point<SimplePoint> {
@Override
public SimplePoint[] neighbors() { /* ... */ }
}
public class SpecialPoint implements Point<SpecialPoint> {
@Override
public SpecialPoint[] neighbors() { /* ... */ }
}
Then:
public <P extends Point<P>> P doStuff(P point) {
P[] neighbors = point.neighbors();
/* ... */
}
If you still need to factorize code between the implementations, then better use an abstract class:
public abstract class Point<P extends Point<P>> {
public abstract P[] neighbors();
public void commonMethod() { /* ... */ }
}
public class SimplePoint extends Point<SimplePoint> { /* ... */ }
public class SpecialPoint extends Point<SpecialPoint> { /* ... */ }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With