2

Consider the abstract class:

public abstract class Animal { ...}

and the interface:

public interface Canine<T extends Animal> {...}

I've defined the concrete classes:

public class Dog extends Animal implements Canine<Dog> {...}
public class Wolf extends Animal implements Canine<Wolf> {...}

I'd like to build a repository class that accesses the database of animals and returns them. I've defined it in the following way:

public interface Repository {
    Option<Dog>  findById(String id, Class<Dog>  type);
    Option<Wolf> findById(String id, Class<Wolf> type);

(note: Option is taken from the vavr library)

This repository is used in the following class:

public abstract AbstractFinderClass<T extends Animal & Canine<T>> {

    private Class<T> animalType;

    public AbstractFinderClass(Class<T> animalType) {
        this.animalType = animalType;
    }

    public Option<T> findMyAnimal(String id) {
        return repository.findById(id, this.animalType);
    }
}

which in turn is implemented in concrete form with:

public class DogFinder extends AbstractFinderClass<Dog> {
    public DogFinder() {
        super(Dog.class);
    }
}

Now, the line return repository.findById(id, this.animalType) causes two errors:

  1. on the second parameter, this.animalType is of type Class<T> while the expected type is Class<Dog>, and these are apparently incompatible;
  2. the return type is expected to be Option<T> while instead I get Option<Dog>

I am afraid I am missing some "little" detail, as I would expect Dog and T to be compatible. Could you help me in fixing the problem?

5
  • 4
    I'm wondering how the Repository class is supposed to be implemented. Shouldn't the findById methods have the same type erasure? Commented Jan 2, 2019 at 14:44
  • 2
    Instead of overloading the same method in the repository class, can you just turn it into a single generic method? basically Option<A> findById<A>(String id, Class<A> type); . Commented Jan 2, 2019 at 14:47
  • 1
    I think you are getting errors because of T is possible to be a type that is not defined in your Repository, so you would try to execute method that doesn't exist. Commented Jan 2, 2019 at 14:52
  • If DogFinder is supposed to be a concrete implementation of AbstractFinderClass, why doesn't it have an extends clause? We can't be sure that your T is Dog. In fact, why does it even have Dog as a generic parameter? That would create confusion with the Dog class. Your code wouldn't actually compile. I suggest a minimal reproducible example. Commented Jan 2, 2019 at 14:56
  • What you're asking for is basically Bloch's "Typesafe Heterogenous Container" implementation -- you should check out Effective Java which describes this approach in detail. In this particular case, you might find this answer to a related question useful. Commented Jan 2, 2019 at 14:59

1 Answer 1

2

The first problem is that you're having an unnecessary type parameter for DogFinder. It's a dog finder, so a type parameter for what it finds is superfluous (the unconventionally named type parameter Dog could perhaps have indicated a problem). It should be:

class DogFinder extends AbstractFinderClass<Dog> {
    public DogFinder() {
        super(Dog.class);
    }
}

Second, your Repository type has methods that are bound to specific types. This makes little sense because you want it to be generic. So you can use just one method, (optionally) making the repository itself generic (in the process solving the signature clash problem):

interface Repository<T extends Animal> {
    Option<T> findById(String id, Class<T> type);
}

Third, unless we're missing context, I believe your Canine type doesn't need to be generic (unless things must be convoluted):

interface Canine {
}

If you need a dedicated canine finder, you can simply change your repository class, like so:

abstract class CanineFinderClass<T extends Animal & Canine>
    implements Repository<T> {...}

As a side note, the DogFinder repository is redundant unless it offers special dog methods, like findAllPuppies(). Otherwise, making AbstractFinderClass concrete should be enough as the type is generic (just an example):

class AnimalFinderClass<T extends Animal> implements Repository<T> {

    Repository<T> repository;

    private Class<T> animalType;


    public AbstractFinderClass(Class<T> animalType) {
        this.animalType = animalType;
    }

    public Option<T> findMyAnimal(String id) {
        return repository.findById(id, this.animalType);
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

the type parameter <T extends Animal> should be moved from the class Repository to the method findById, since Repository works with all animal types.
@AlexeiKaigorodov The idea is that a given repository may work with any animal type, not all animal types. So on instantiation, the type argument will define which animal type. So when reusing the repo, you get the same type (otherwise the animalType parameter would have to be passed in on each method call)
the idea of the topic starter was that the repository works with all types while AbstractFinderClass works with any. Otherwise, there is no need for 2 different classes each working with some concrete animal type.
@AlexeiKaigorodov That's exactly the point of my side note (I suspect we're saying the same thing). A given type can work with any animal type (hence the type itself is generic), but when an instance of a repository is created, it is bound to one concrete animal type (hence the method is not generic). The OP's biggest problem was the (probably accidental) type parameter on DogFinder
my bad on the extra parameter on DogFinder, sorry: I delete a piece of code by mistake :/ see updated question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.