Craig Otis
Craig Otis

Reputation: 32054

Providing concrete class instance to generic method accepting T, where T extends concrete class

I have a class ShapeDescriber that looks like this:

public class ShapeDescriber<T extends Shape> {
    public void describe(T shape) {
        System.out.println("Its color is " + shape.getColor());
    }
}

I use T so that subclasses can do something like:

public class CircleDescriber<T extends Circle> extends ShapeDescriber<T> {
    public void describe(T circle) {
        super.describe(circle);
        System.out.println("Its radius is " + circle.getRadius());
    }
}

Ultimately, I want this Describer type hierarchy to match the type hierarchy of my model.

The problem that I run into, is that inside the CircleDescriber, I can't pass a Circle to my describe() method! When I try this:

public class CircleDescriber ... {
    ...
    public void printATest() {
        Circle c = new Circle(Colors.GREEN, 10);
        this.describe(c);
    }
}

There's a compilation error on my describe() call, because:

The method describe(T) in the type CircleDescriber<T> is not applicable for the arguments (Circle)

Wait. What? I feel like this class knows that every instance of a T will be a subclass of Circle, no?

Edit: I have created a Gist here for easy copypasting: https://gist.github.com/craigotis/135f88b1ce8beca07400

Note the above Gist will fail to compile.

Upvotes: 0

Views: 912

Answers (2)

Tobb
Tobb

Reputation: 12205

I think this should work as is, but depending a bit on how you instantiate the CircleDescriber. This should work:

new CircleDescriber<Circle>().describe(circle);

The reason that this does not compile is that you have the test method inside the CircleDescriber, with a call to this. Since the compiler does not know the generic parameter of this, it can't say for sure that passing a Circle to it will work (what if this refers to a CircleDescriber parameterized to a subclass of Circle?) Change this with new CircleDescriber<Circle>() in your test-method, and it shold compile.

But, here you see that you are actually specifying that you are describing a Circle twice, both in the class name and in the generic parameter. This should not be necessary, at least as long as Circle is a leaf node in your hierarchy. Instead, do this:

(Keep ShapeDescriber as is)

public class CircleDescriber extends ShapeDescriber<Circle> {
    @Override
    public void describe(Circle circle) {
        super.describe(circle);
        System.out.println("Its radius is " + circle.getRadius());
    }
}

Upvotes: 3

BobTheBuilder
BobTheBuilder

Reputation: 19284

Declaring :

public class CircleDescriber<T extends Circle> extends ShapeDescriber<T>

Means that

CircleDescriber.describe()

gets some class extending Circle. For example:

Having a class: public class RedCircle extends Circle, you can declare:

CircleDescriber describer = CircleDescriber<RedCircle> meaning that describer can get RedCircle only (and not Circle). That's why it is not allowed.

Changing to:

public class CircleDescriber extends ShapeDescriber<Circle>

Would work if you can use Circle only.

Upvotes: 1

Related Questions