抽象类中的类型安全问题

时间:2015-03-09 23:17:39

标签: java generics static abstract

我最近开始在Java上开发一个新项目,该项目将拥有一个本地数据库。作为设计的一部分,我创建了一个AbstractEntity类 - 它用作数据库中行(或潜在行)的对象表示。

我在这个设计的早期遇到了一些问题,并且想确保我不会走坏路。我遇到问题的一个特殊方法如下:

public ArrayList retrieveEntities(String sql)
{
    ArrayList ret = new ArrayList();

    String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

    try (Connection conn = DatabaseUtil.createDatabaseConnection();
      Statement s = conn.createStatement();
      ResultSet rs = s.executeQuery(query))
    {
        while (rs.next())
        {
            AbstractEntity entity = factoryFromResultSet(rs);
            ret.add(entity);
        }
    }
    catch (SQLException sqle)
    {
        Debug.logSqlException(query, sqle);
    }

    return ret;
}

这种方法背后的想法是有一种从数据库中检索东西的通用方法,其中我唯一需要传递的是SQL的条件。目前它正常工作,但我有两个问题:

1)输入安全性

我似乎无法在不导致编译器错误的情况下参数化此方法。 ArrayList<AbstractEntity>并不好,我似乎无法让ArrayList<? extends AbstractEntity>工作。当我尝试后者时(这对我来说很有意义),以下行给出了编译器错误:

ArrayList<PlayerEntity> list = new PlayerEntity().retrieveEntities("1 = 1");

错误是'类型不匹配:无法从ArrayList <capture#1-of ? extends AbstractEntity>转换为ArrayList<PlayerEntity>'

有没有办法可以直接从抽象类中引用超类?这个方法不是静态的,因为你不能实例化一个抽象类(它没有构造函数),要调用它,我必须总是有一个扩展类。那么为什么我不能引用它的类型呢?

2)静态

理想情况下,我希望这种方法是静态的。这样我就可以直接调用PlayerEntity.retrieveEntities(),而不是仅仅调用它来调用它。但是,由于它涉及抽象方法,我不能这样做,所以我坚持不懈。

上面的两个抱怨都在我的脑袋里响起警钟。有没有更好的方法来设计这个以避免这些问题,或者更好的是,我是否缺少这些问题的直接解决方案?

2 个答案:

答案 0 :(得分:0)

第一点:如果你的方法是静态的,那么使用多态就没有意义了。多态性在运行时工作,但是使方法静态强制您在编译时指示动态类型,这是无意义的。

关于方法的返回类型,您可能需要先将其设置为ArrayList<? extends AbstractEntity>,否则您只是说要返回任何对象(即ArrayList<Object>)。 在此之后,您必须创建与返回类型相同类型的局部变量,以便不会出现编译错误。

现在,您如何填充此系列? 我将给你两个提示:

  1. 您可以使用Reflection,特别是您可以通过在运行时选择要实例化的类来调用类构造函数(使用getClass())。
  2. 您可以利用Template method pattern最终设计灵活,不会出现重复的代码。
  3. 但是,一般而言,您遇到的问题已经解决了。因此,如果您只是在寻找一个现成的解决方案,您可以查看一个像Hibernate这样的ORM框架。

答案 1 :(得分:0)

我认为你正在重新发明轮子。 ORMs (Object-Relational Mappers)已存在多年,并且已证明非常有用。

但是,他们没有防弹。由于他们打算解决的问题非常困难(我的意思是object-relational impedance mismatch),解决方案通常也会遇到困难。

为了灵活地开展工作,一些ORM会影响性能,而另一些则会影响使用的简单性等。我的意思是这里没有完美的解决方案。

我想向您介绍我在不同项目中使用过的三种不同的ORM:

这里有许多比较和基准,深入探讨了这一主题。

Hibernate是最广泛使用的,它功能强大且功能强大,提供了很大的灵活性,并且如果使用得好也能很好地运行。关于缺点,它有一个陡峭的学习曲线,它对于初学者来说有点复杂,一般来说,使用Hibernate的解决方案最终会永远使用Hibernate,因为它很容易让Hibernate无意中潜入您的业​​务层。

ActiveJDBC不是很受欢迎,但它是最好的Java ActiveRecord解决方案。如果您来自Ruby,这是您的选择。它的API非常流畅和富有表现力,使用它的代码非常易于阅读和维护。它非常简单,而且非常简单框架。

E-Bean非常强大,它的API流畅而富有表现力,并且该产品提供了适应性行为,可以即时优化查询。它使用简单,使用它的代码具有良好的可读性,易于维护。

关于类型安全,我通常采用这种方法:

public class AbstractRepository<T extends AbstractEntity> {

    protected final Class<T> entityClazz;

    protected AbstractRepository() {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        this.entityClazz = (Class<T>) paramType.getActualTypeArguments[0];
        // TODO exception handling
    }

    public List<T> list(String sql) { // retrieveEntities => very long name
        List<T> ret = new ArrayList<>();

        String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

        try (Connection conn = DatabaseUtil.createDatabaseConnection();
            Statement s = conn.createStatement();
            ResultSet rs = s.executeQuery(query)) {
            while (rs.next()) {
                T entity = factoryFromResultSet(rs);
                ret.add(entity);
            }
        } catch (SQLException sqle) {
            Debug.logSqlException(query, sqle);
        }

        return ret;
    }

    protected T factoryFromResultSet(ResultSet rs) {
        // Create new entity instance by reflection
        T entity = clazz.getConstructor().newInstance();

        // TODO exception handling 
        // Fill entity with result set data

        return entity;
    }
}

我已经声明了一个抽象的存储库类,需要使用正确的参数类型进行扩展:

public class Person extends AbstractEntity {
}

public class PersonRepository extends AbstractRepository<Person> {
}

PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");

我通常将生成实体的代码与实际实体分开。代码,否则实体最终会承担很多责任并做太多工作。但是,ActiveRecord方法通过让实体完成所有工作来解决这个问题,并且它是Java之外的一个非常受欢迎的选择。