摘要 class 中的类型安全问题
Issue with type safety within abstract class
我最近开始在 Java 开展一个新项目,该项目将有一个本地数据库。作为设计的一部分,我创建了一个 AbstractEntity class - 这是数据库中一行(或潜在行)的对象表示。
虽然我已经 运行 在这个设计的早期解决了一些问题,但我想确保我不会走上一条糟糕的道路。我遇到问题的一种特殊方法如下:
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");
错误是'Type mismatch: cannot convert from ArrayList<capture#1-of ? extends AbstractEntity>
to ArrayList<PlayerEntity>
'
有没有一种方法可以直接从抽象 class 中引用 superclass?这个方法不是静态的,因为你不能实例化一个抽象的 class (它没有构造函数),要调用这个我必须 always 有一个扩展 class .那为什么我不能引用它的类型?
2) 静态
理想情况下,我希望此方法是静态的。这样我就可以直接调用 PlayerEntity.retrieveEntities() ,而不是创建一个对象来调用它。但是,由于它指的是抽象方法,我不能这样做,所以我坚持使用它。
以上两种抱怨都在我脑海中敲响了警钟。是否有更好的设计方法来避免这些问题,或者更好的是,是否有直接解决我遗漏的这些问题的方法?
第一点:如果你让你的方法是静态的,那么使用多态就没有意义了。多态性在运行时起作用,但是将方法设为静态会强制您在编译时指示动态类型,这是无意义的。
关于方法的 return 类型,您可能需要先将其设置为 ArrayList<? extends AbstractEntity>
,否则您只是说您 return 任何对象(即 ArrayList<Object>
).
在此之后,您必须创建一个与 return 类型相同类型的局部变量,以免出现编译错误。
现在,您如何填充此集合?
我要给你两个提示:
- 您可以使用 Reflection,特别是您可以通过在运行时选择要实例化的 class 来调用 class 构造函数(使用
getClass()
)。
- 您可以利用 Template method pattern 来实现灵活的设计并且没有重复的代码。
总的来说,您遇到的问题已经解决了。所以,如果你只是在寻找一个现成的解决方案,你可以看看像 Hibernate.
这样的 ORM 框架。
我认为你是在重新发明轮子。 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;
}
}
我已经声明了一个抽象存储库 class,需要使用正确的参数类型对其进行扩展:
public class Person extends AbstractEntity {
}
public class PersonRepository extends AbstractRepository<Person> {
}
PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");
我通常将生成实体的代码与实际实体的代码分开,否则实体最终会承担很多责任并做太多工作。然而,ActiveRecord 方法通过让实体完成所有工作来解决这个问题,它是超越 Java.
世界的一个非常流行的选择。
我最近开始在 Java 开展一个新项目,该项目将有一个本地数据库。作为设计的一部分,我创建了一个 AbstractEntity class - 这是数据库中一行(或潜在行)的对象表示。
虽然我已经 运行 在这个设计的早期解决了一些问题,但我想确保我不会走上一条糟糕的道路。我遇到问题的一种特殊方法如下:
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");
错误是'Type mismatch: cannot convert from ArrayList<capture#1-of ? extends AbstractEntity>
to ArrayList<PlayerEntity>
'
有没有一种方法可以直接从抽象 class 中引用 superclass?这个方法不是静态的,因为你不能实例化一个抽象的 class (它没有构造函数),要调用这个我必须 always 有一个扩展 class .那为什么我不能引用它的类型?
2) 静态
理想情况下,我希望此方法是静态的。这样我就可以直接调用 PlayerEntity.retrieveEntities() ,而不是创建一个对象来调用它。但是,由于它指的是抽象方法,我不能这样做,所以我坚持使用它。
以上两种抱怨都在我脑海中敲响了警钟。是否有更好的设计方法来避免这些问题,或者更好的是,是否有直接解决我遗漏的这些问题的方法?
第一点:如果你让你的方法是静态的,那么使用多态就没有意义了。多态性在运行时起作用,但是将方法设为静态会强制您在编译时指示动态类型,这是无意义的。
关于方法的 return 类型,您可能需要先将其设置为 ArrayList<? extends AbstractEntity>
,否则您只是说您 return 任何对象(即 ArrayList<Object>
).
在此之后,您必须创建一个与 return 类型相同类型的局部变量,以免出现编译错误。
现在,您如何填充此集合? 我要给你两个提示:
- 您可以使用 Reflection,特别是您可以通过在运行时选择要实例化的 class 来调用 class 构造函数(使用
getClass()
)。 - 您可以利用 Template method pattern 来实现灵活的设计并且没有重复的代码。
总的来说,您遇到的问题已经解决了。所以,如果你只是在寻找一个现成的解决方案,你可以看看像 Hibernate.
这样的 ORM 框架。我认为你是在重新发明轮子。 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;
}
}
我已经声明了一个抽象存储库 class,需要使用正确的参数类型对其进行扩展:
public class Person extends AbstractEntity {
}
public class PersonRepository extends AbstractRepository<Person> {
}
PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");
我通常将生成实体的代码与实际实体的代码分开,否则实体最终会承担很多责任并做太多工作。然而,ActiveRecord 方法通过让实体完成所有工作来解决这个问题,它是超越 Java.
世界的一个非常流行的选择。