I have an abstract class:
public abstract class AbstractPoint<PointType extends AbstractPoint<PointType>> {
public abstract double getX();
public abstract double getY();
public abstract double rho();
// Find the distance between two points
public final <T extends AbstractPoint<T>> double distance(T other) {
return vectorTo(other).rho();
}
// Find vector from one point to another.
public abstract <T extends AbstractPoint<T>> PointType vectorTo(T other);
// other methods ...
}
Classes OrthogonalPoint
and PolarPoint
extend the abstract class:
public class OrthogonalPoint extends AbstractPoint<OrthogonalPoint> {
private double x;
private double y;
public OrthogonalPoint(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return this.x; }
public double getY() { return this.y; }
public double rho() { return Math.sqrt(Math.pow(x, 2) Math.pow(y, 2)); }
public double theta() { return Math.atan2(this.y, this.x); }
public <T extends AbstractPoint<T>> OrthogonalPoint vectorTo(T other) {
return new OrthogonalPoint(other.getX() - this.x, other.getY() - this.y);
}
}
public class PolarPoint extends AbstractPoint<PolarPoint> {
private double rho;
private double theta;
public PolarPoint(double x, double y) {
this.rho = Math.sqrt(Math.pow(x, 2) Math.pow(y, 2));
this.theta = Math.atan2(y, x);
}
public double getX() { return this.rho() * Math.cos(this.theta()); }
public double getY() { return this.rho() * Math.sin(this.theta()); }
public double rho() { return this.rho; }
public double theta() { return this.theta; }
public <T extends AbstractPoint<T>> PolarPoint vectorTo(T other) {
return new PolarPoint(other.getX() - this.getX(), other.getY() - this.getY());
}
}
With this code, I can correctly calculate the distance between points no matter whether they are of same type (orthogonal or polar).
Now I add another class called Route
:
public class Route {
private final List<? extends AbstractPoint<?>> points = new ArrayList<>();
// 'point' in 'this.points.add(point)' underlined red
public <T extends AbstractPoint<T>> void appendPoint(T point) { this.points.add(point); }
public <T extends AbstractPoint<T>> void removePoint(T point) { this.points.remove(point); }
// 'point' in '.distance(point)' underlined red
public double routeDistance() {
return this.points.stream().skip(1).mapToDouble(point ->
this.points.get(this.points.indexOf(point) - 1).distance(point)).sum();
}
}
A route is an ordered number of points whose length is defined as the sum of the distances of the points in the sequence. So, if a route consists of three points (p1, p2, and p3) the distance of the route is p1.distance(p2) p2.distance(p3)
.
However, some places (look at the comments in Route
) in my code are underlined red:
// in appendPoint function
Required type: capture of ? extends AbstractPoint<?>
Provided: T
// in routeDistance function
Required type: T
Provided: capture of ? extends AbstractPoint<?>
I want to be able to add any type of point to the points list and the call routeDistance
function to calculate the length of the route. What is wrong with my solution - how should I change my generics (in any class)?
CodePudding user response:
First, note that you are both adding data to the points
list, and taking data out of the list, so you should not use extends
or super
. (See also: PECS)
private final List<AbstractPoint<?>> points = new ArrayList<>();
Next, you will see that there is an error at the .distance(point)
call. This is because there is no type that can be used as the type parameter T
of the distance
method, that also satisfies the constraint T extends AbstractPoint<T>
. Note that the type of point
is AbstractPoint<?>
, and AbstractPoint<?>
can't be T
, because AbstractPoint<?>
does not extend AbstractPoint<AbstractPoint<?>>
. See the problem?
From what I can see, the way you have written distance
does not allow you to find the distance between two AbstractPoint
s. One of the points must be a concrete type.
In fact, distance
and vectorTo
doesn't actually need the generic parameter. It can just take a AbstractPoint<?>
:
public final double distance(AbstractPoint<?> other) {
return vectorTo(other).rho();
}
public abstract PointType vectorTo(AbstractPoint<?> other);
This now allows you to pass in AbstractPoint<?>
to distance
.
Side note: your current implementation of routeDistance
using indexOf
is not very efficient, as it goes over the list once for every point in the list. You can do this with a zip operation instead. Using the zip
method in the linked answer:
return zip(points.stream(), points.stream().skip(1), AbstractPoint::distance)
.mapToDouble(x -> x).sum();