我可以这样做通用的东西吗?

时间:2009-11-18 15:44:24

标签: java generics dao

看起来我遗漏了Java Generics的东西,因为我认为这很简单,在我看来无法完成。也许你可以帮忙...

这是一个场景:我正在使用简单的CRUD操作编写一个通用的抽象DAO,因此我应用程序的每个特定DAO都可以免费使用它:

public abstract DefaultDAO<T,V> {
  private EntityManager manager;

  public BaseDAO(EntityManager em) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    // Now, here is the problem. 
    // EntityManager signature is: public <T> T find(Class<T> entityClass, Object primaryKey);
    // So I must provide the type of the object this method will be returning and 
    // the primary key.
    // resulting object will be T typed and pk type is V type (although is not needed to type it since the method expects an Object)
    // So... I expected to be allowed to do something like this
    return manager.find(T, pk); // But it's not allowed to use T here. T is not an instance
  }   
}

现在我要去实现一个特定的DAO:

public PersonDAO extends DefaultDAO<PersonEntity, Long> {
  public PersonDAO(EntityManager em) {
    super(em);
  }
  // CRUD methods are inherited
}

我的DAO的客户端代码是:

EntityManager manager = ...
PersonDAO dao = new PersonDAO(manager);
Long pk = .....
PersonEntity person = dao.find(pk); // DAO would return a PersonEntity

当客户端执行代码时,BaseDAO知道它必须返回的实体类型以及该实体的主键类型,因为我在特定的dao上设置它,但我不知道如何编写read()方法正确。

希望你能提供帮助。非常感谢!

5 个答案:

答案 0 :(得分:14)

您正在尝试使用类型参数,就好像它是正常表达式一样。你不能这样做。在其他语言中,您可以在执行时获取类型参数的类,但由于类型擦除而无法在Java中使用。

在这种情况下,您需要在执行时传入Class<T> - 例如传递给构造函数:

public abstract class DefaultDAO<T, V> {

    private EntityManager manager;
    private final Class<T> clazz;

    public DefaultDAO(EntityManager em, Class<T> clazz) {
        this.manager = em;
        this.clazz = clazz;
    }

    public T read(V pk) {
        return manager.find(clazz, pk);
    }
}

答案 1 :(得分:3)

Java在运行时不再具有泛型类信息(称为Type Erasure)。我所做的是给我的抽象Daos提供泛型类的实例。

public abstract DefaultDAO<T,V> {

  protected Class<T> genericClass;
  private EntityManager manager;

  protected BaseDAO(EntityManager em, Class<T> implclass) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    return manager.find(this.getGenericClass(), pk); // But it's not allowed to use T here. T is not an instance
  }

  public Class<T> getGenericClass()
  {
    return genericClass;
  }
}

public class Blubb
{
    private String id;

    // Getters and Stuff...
}

public class BlubbDao extends DefaultDAO<Blubb, String>
{
    public BlubbDao(EntityManager em)
    {
        super(em, Blubb.class);
    }
}

我不能保证它会开箱即用,但我希望你明白这一点。

答案 2 :(得分:3)

有一种方法可以使用反射来完成此操作,只要您的类遵循泛型方面的一致类层次结构(即基类和具体类之间的继承层次结构中的任何中间类都使用相同的泛型参数相同的顺序)。

我们在抽象类中使用这样的东西,定义为HibernateDAO,其中T是实体类型,K是PK:

private Class getBeanClass() {
    Type daoType = getClass().getGenericSuperclass();
    Type[] params = ((ParameterizedType) daoType).getActualTypeArguments();
    return (Class) params[0];
}

它有点闻起来,但是从构造函数中的具体实现中传递.class的ickiness,因为坚持要求你保持你的类型层次结构一致。

答案 3 :(得分:3)

我认为你可以这样做:

 public <T> T find(Class<T> clazz, Object id) {
        return entityManager.find(clazz, id);
    }

答案 4 :(得分:2)