Home > Enterprise >  Java generics problem - required T, provided capture or vice-versa
Java generics problem - required T, provided capture or vice-versa

Time:10-03

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 AbstractPoints. 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();
  • Related