Eleonora Ciceri
Eleonora Ciceri

Reputation: 1798

Java generics, Interface and Class inheritance

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?

Upvotes: 2

Views: 94

Answers (1)

ernest_k
ernest_k

Reputation: 45329

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);
    }
}

Upvotes: 2

Related Questions