我正在尝试为我的应用程序编写通用数据访问层。我有多个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<?>)有关,但我无法真正掌握哪种方式。
我该如何修复我的代码?或者我的方法是错的?
答案 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注释时,它们在继承方面效果不佳)。