carcaret
carcaret

Reputation: 3358

Generic types and polymorphism in interfaces

I'm trying to generify some calls to DB using MyBatis, but I've hit a wall so far.

Let's say I have this entities and mappers:

public interface Entity {

    <T extends Entity> Class<Insertable<T>> mapper();
}

public interface Insertable<T extends Entity> {

    void insert(T entity);
}

public interface ClientMapper extends Insertable<Client> {

    void insert(Client client);
}

public interface CampaignMapper extends Insertable<Campaign> {

    void insert(Campaign campaign);
}

public class Client implements Entity {

    private final Long id;

    public Client(final Long id) {
        this.id = id;
    }

    @Override
    public <T extends Entity> Class<Insertable<T>> mapper() {
        return ClientMapper.class; // Incompatible types error
    }

I'm getting this compilation error even though ClientMapper is of type Insertable<Client>, being Client a type of Entity.

The objective is getting to the following type-safe code:

public class MapperOperation {

    public static void insert(Entity entity) {
        insert(entity, entity.mapper());
    }

    private static <V extends Entity, K extends Insertable<V>> void insert(V entity, Class<K> mapperClass) {
        try (SqlSession session = PersistenceManager.get().openSession()) {
            K mapper = session.getMapper(mapperClass);
            mapper.insert(entity);
            session.commit();
        } catch (Exception e) {
            log.error("Could not insert entity {}", entity, e);
        }
    }
}

This way, I can just call the method insert with an instance of any Entity and then ask him to give me his mapper so that I can insert it.

Is this possible? Am I doing something wrong?

Upvotes: 2

Views: 723

Answers (1)

rom1v
rom1v

Reputation: 2969

It should compile with these changes:

public interface Entity {
    <T extends Entity> Class<? extends Insertable<T>> mapper();
}    

// ...

public <T extends Entity> Class<? extends Insertable<T>> mapper() {
    return ClientMapper.class;
}

You cannot assign Class<ClientMapper> to Class<Insertable<T>>, for the same reason you cannot assign List<Integer> to List<Number>.

If you could, it would be unsafe:

List<Integer> li = new ArrayList<>();
List<Number> ln = li; // does not compile
ln.add(3.14);
Integer i = li.get(0); // it's a Double!

EDIT: This is not the only issue: the method returns something which depends on T, but you return another one. The problem can be simplified:

class A {}
class B extends A {}
class C extends A {}

<T extends A> T getValue() {
    return new C();
}

Here, T might be resolved to any subtype of A, so we cannot return an instance of C because T might be resolved to B (e.g. by calling this.<B>getValue()), and C is not a subtype of B.

Upvotes: 4

Related Questions