泛型和Java:“扩展”如何工作?

时间:2010-02-09 12:46:11

标签: java generics

我正在尝试为我的应用程序编写通用数据访问层。我有多个hibernate实体,它们大多是相同的,在Java中表示为类层次结构(它们不是作为Hibernate中的层次结构实现的):

public abstract class Entity1 {
    // some implementation
}
public class Entity2 extends Entity1 {
    // some implementation
}
public class Entity3 extends Entity1 {
    // some implementation
}

这些实体的DAO大致相同(除了方法的类型签名,以及要求Hibernate的类)。我希望能够像这样写一个通用的DAO:

public interface EntityDao<T extends Entity1> {
    List<T>getEntities();
    void saveOrUpdateEntity(T entity);
}

public class EntityDaoImpl implements EntityDao<Entity1> {
    private final HibernateTemplate hibernateTemplate;

    private final Class<DBEnumerationDto> clazz;

    public DBEnumerationDaoHibernateImpl(SessionFactory sessionFactory, Class<DBEnumerationDto> clazz) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
        this.clazz = clazz;
    }

    @Override
    public List<Entity1> getEntities() {
        return this.hibernateTemplate.loadAll(this.clazz);
    }
    @Override
    public void saveOrUpdateEntity(Entity1 entity) {
        this.hibernateTemplate.saveOrUpdate(entity);
    }
}

到目前为止,这么好。但是使用它时会出现问题:

Entity1 test = new Entity1();
Entity1Dao<? extends Entity1> dao = ...; // get the dao forthe current operation
dao.saveOrUpdate(test);

这会产生编译器错误:The method saveOrUpdateEntity(capture#5-of ? extends Entity1) in the type EntityDao<capture#5-of ? extends Entity1> is not applicable for the arguments (Entity1)

我想这个问题与Java Generics: casting to ? (or a way to use an arbitrary Foo<?>)有关,但我无法真正掌握哪种方式。

我该如何修复我的代码?或者我的方法是错的?

2 个答案:

答案 0 :(得分:2)

想一想关于问号(通配符)在失败示例的第二行中的含义。

您已获得Entity1Dao,其中包含未知的通用参数(唯一知道该参数是Entity1的子类)。因此,实现如下实现是完全合法的:

Entity1Dao<? extends Entity1> dao = getEntityDao();

private Entity1Dao<Entity2> getEntityDao()
{
    return new Entity1Dao<Entity2>(); // Or whatever (the construction itself is irrelevant)
}

由于通配符,分配Entity1Dao<Entity2>是完全合法的。现在,您转到下一行,尝试调用dao.saveOrUpdate(),传入Entity1类型的对象。

这不起作用 - 正如我们刚才所示,dao在Entity2上进行了参数化,因此只有具体的方法saveOrUpdate(Entity2 entity)!因此编译器为您提供类型警告的原因。


总结,您的问题是“extends”关键字,它允许通配符参数成为您实际类型的子类,因此无法处理它。如果您将通配符更改为使用super(即<? super Entity1>),则会编译,因为编译器可以确定无论实际泛型类型如何,saveOrUpdate方法都将接受Entity1类型的参数。

通常情况下,与某些确切类型相比,您需要使用相同的参数 super extends ,这意味着您根本不能使用通配符。在这种情况下,您可能希望将整个方法放在类的具体类型上,如下所示:

public <T extends Entity1> void saveEntityExample(T test)
{
   Entity1Dao<T> dao = getEntityDao();
   dao.saveOrUpdate(test);
}

private <T extends Entity1> Entity1Dao<T> getEntityDao()
{
   // Get the DAO however
}

您可能还想查看Josh Bloch在this answer中的话题,特别是要了解“PECS”的概念。与“扩展”总是答案的类不同,当涉及到泛型时,需要考虑它们是指“扩展”还是“超级”,而助记符是记住一般规则的有用方式。

答案 1 :(得分:0)

您不应该在泛型类的实际实例化中提供extends关键字,因为它需要知道它实际上是什么类型。您必须使用该类型或通配符,这意味着

Entity1Dao<Entity1>
// or
Entity1Dao<?>

如果您正在使用Hibernate并希望实现通用DAO,则可能根本不需要Entity超类,因为Hibernate Template可以与任何已注册实体的类一起使用。我曾经尝试过一次Entity Superclass方法,但它只是一个巨大的通用麻烦而没有太大的好处(特别是在使用Hibernate注释时,它们在继承方面效果不佳)。