博客, ACW的markdown好像不太好用, & nbsp;和====标记没有显示
前情提要
某学校期末又有XX管理系统. 在编写过程中, 小S发现所有的Dao层总是在运行着一些类似的代码, 即使是在将业务分给conn和service之后仍是如此. 如此, 是否可以创建一个ObjectDao, 通过泛型编程 + 可变参数来实现(好像哪里不太对劲).
MySql基本语句
SELECT columns FROM database WHERE column = value and …
INSERT INTO database(values) VALUE(values), …
DELETE FROM database WHERE column = value and …
上述所需要的参数为列名, 数据库名, 条件. 列名使用长字符串或者set; 数据库名使用字符串; 条件采用Pair键值对完成, 同时使用可变参数来完成零个或多个条件的情况
但是在接口文件中存在警告: 来自形参化vararg类型的可能的堆污染.
解决方法为:
1. 安全删除
2. 将vararg形参转化为数组
堆污染
大部分参数化类型,例如ArrayList[HTML_REMOVED] 和 List[HTML_REMOVED],都属于非具体化类型(non-reifiable types)。非具体化类型是指在运行时(runtime)并不完整的类型。在编译时,非具体化类型经过了一个名为“类型擦除”的过程,编译器删除了与类型参数相关的信息。这将保证Java运行库与那些诞生在Java泛型之前的应用程序之间的二进制兼容性。由于在编译时,类型擦除操作删除了来自参数化类型的相关信息,因此它们是不完整的。
一个参数化类型的变量引用一个非参数化类型的对象,将会导致堆污染。这种情况只会在程序执行了一些在编译时会出现未经检查的警告(unchecked warning)的操作时发生。不管是在编译时(符合编译时类型检查规则的约束),还是在运行时,如果无法验证一个表示参数化类型的操作(例如:类型强转、方法调用)是否正确,就会产生一个未经检查的警告。
简单的说就是当一个泛型类型变量赋值给不是泛型类型变量,这种错误在编译期间能被编译器警告,但是可以忽略,直到运行时报错。
- 下面查看一个错误栗子
List l = new ArrayList<Number>();
List<String> ls = l; // unchecked warning
l.add(0, new Integer(42)); // another unchecked warning
String s = ls.get(0); // ClassCastException is thrown
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]在类型擦除的时候,类型==ArrayList[HTML_REMOVED]==和==List[HTML_REMOVED]==将会分别变为==ArrayList==和==List==。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]变量ls具有参数化类型==List[HTML_REMOVED]==。当变量==l==引用的类型==List==被分配给变量==ls==时,编译器将会产生一个未经检查的警告;编译器无法确定,而且我们也知道Java虚拟机(JVM) 在运行时也无法确定变量l是否引用了类型==List[HTML_REMOVED]==,于是堆污染就产生了。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]因此,在编译时,编译器在add语句处生成了又一个未经检查的警告。编译器无法确定变量l引用的类型是==List[HTML_REMOVED]==还是==List[HTML_REMOVED]==(或者其他可能产生堆污染的情况)。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]不过,编译器不会在get语句处生成一个警告或错误。这个语句是有效的;程序将调用==List[HTML_REMOVED].get==方法并返回一个字符串对象。不过,在运行时,get语句将会抛出一个类型转换异常==java.lang.ClassCastException==。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]更详细地说,堆污染发生在静态类型为==List[HTML_REMOVED]== 的List对象l被分配给另一个具有不同静态类型 ==List[HTML_REMOVED]== 的List对象ls时。不过编译器仍然允许该分配,以便于向后兼容那些不支持泛型的Java SE 版本。由于类型擦除的缘故,类型==List[HTML_REMOVED]== 和 ==List[HTML_REMOVED]== 都会变为==List==。这样,编译器允许对象l以原始类型List的方式被分配给对象ls。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]此外,堆污染还发生在==l.add==方法被调用时。add方法的第二个形式参数的静态类型是==String==,但该方法被调用时使用的却是一个不同类型(Integer)的实际参数。不过,编译器仍然允许其通过。由于类型擦除的缘故,add方法的第二个形式参数的类型变成了Object,这里的add被定义为==List[HTML_REMOVED].add(int, E)==,而Object是Number和String的公共父类。在类型擦除之后,l.add可以添加一个类型为Object的对象,当然也包括它的子类Integer,所以编译器允许其通过编译。
可变参数方法和非具体化的形式参数
引入一个可变参数的方法
public static <T> void addToList (List<T> listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]当遇到可变参数的方法时,编译器会将可变参数形式的形式参数转化为一个数组。但是,Java编程语言并不允许创建泛型数组。在方法==ArrayBuilder.addToList==中,编译器将可变参数形式的形式参数T… elements转化为一个数组T[] elements。但是由于类型擦除的缘故,编译器会将其转换为Object[] elements,这时,堆污染将可能产生。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]方法==ArrayBuilder.faultyMethod==展示了对于这些方法为什么编译器会给出警告。方法中的第一条语句将可变参数l分配给了Object数组objectArgs:
Object[] objectArray = l;
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]这条语句可能引入堆污染,一个与可变参数l的参数化类型相匹配的值可以被赋给变量objectArray,从而也赋给变量l。不过,编译器并不会在这条语句处产生一个未经检查的警告, 因为在将可变参数形式的形式参数==List[HTML_REMOVED]… l==转化为形式参数==List[] l==时,编译器已经产生了一个警告。这条语句是有效的,变量l具有Object[]的子类型List[]。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]因此,正如下面这条语句所示,如果你将一个任何类型的List对象赋值给objectArray数组中的任何元素,编译器并不会发出一个错误或警告:
objectArray[0] = Arrays.asList(new Integer(42));
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]这条语句将一个Integer类型的List对象赋值给objectArray的第一个元素。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]假如你使用下面的语句调用ArrayBuilder.makeArray方法:
ArrayBuilder.faultyMethod(Arrays.asList(“Hello!”), Arrays.asList(“World!”));
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]在运行时,Java虚拟机(JVM)将会在下面这条语句的地方抛出一个类型转换异常(java.lang.ClassCastException):
String s = l[0].get(0); // 此处抛出异常ClassCastException
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]变量l中的第一个元素具有的类型为List[HTML_REMOVED],但是这条语句期望的是一个具备List[HTML_REMOVED]类型的对象。
[HTML_REMOVED][HTML_REMOVED][HTML_REMOVED][HTML_REMOVED]注意: 在Java SE 5和Java SE 6中,调用非具体化形式参数的可变参数方法的程序员有责任去确定哪些情况可能导致堆污染。 然而,如果程序员没有编写过类似这样的方法,那么他/她也很难确定。在Java SE 7中,编写这些可变参数方法的程序员有责任确保他们能够正确处理可变参数形式的形式参数,并且不会导致堆污染的发生。
转载自CSDNhttps://blog.csdn.net/palmtale/article/details/9302711
回到题目
我们已经正确认识到了堆污染, 如果我们能够正确处理可变参数形式的形式参数,并且不会导致堆污染的发生。继续完成代码
ObjectDaoImpl
package dao.impl;
import dao.ObjectDao;
import jdk.internal.net.http.common.Pair;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class ObjectDaoImpl implements ObjectDao {
@Override
public ResultSet select(Connection conn, PreparedStatement pstmt,
String columns, String database, Pair<String, Object>... condition) throws SQLException {
ResultSet resultSet = null;
String sql = "SELECT ? FROM ?";
// 如果条件集不为空
if (condition.length > 0) {
// 添加条件
sql = appendCondition(sql, condition);
}
pstmt = conn.prepareStatement(sql);
// 设置查询行名和数据库名
if (columns.length() == 0) columns = "*"; // 如果查询的列名为空, 就默认查询全部
pstmt.setString(1, columns);
pstmt.setString(2, database);
// 执行查询
resultSet = pstmt.executeQuery();
pstmt.close();
return resultSet;
}
@Override
public void delete(Connection conn, PreparedStatement pstmt,
String database, Pair<String, Object>... condition) throws SQLException {
// 删除必须存在限制条件, 若不存在则产生错误
if (condition.length <= 0) {
throw new RuntimeException();
}
String sql = "DELETE FROM ?";
sql = appendCondition(sql, condition);
// 设置数据库名
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, database);
// 执行更新
pstmt.executeUpdate();
}
/**
* 为sql语句添加条件集合
* @param sql 原sql语句
* @param condition 条件集
* @return 新sql语句
*/
private String appendCondition(String sql, Pair<String, Object>... condition) {
StringBuilder sqlBuilder = new StringBuilder(sql);
sqlBuilder.append(" WHERE ");
for (int i = 0; i < condition.length; i++) {
// 除第一个之外, 其他在前面添加AND
if (i != 0) sqlBuilder.append(" AND ");
sqlBuilder.append(condition[i].first).append(" = ");
// 只判断了字符串和数字
String className = condition[i].second.getClass().getName();
if(className.equals("String")) {
sqlBuilder.append("'").append(condition[i].second).append("'");
}
else if (className.equals("Integer")) {
sqlBuilder.append(condition[i].second.toString());
}
else if (className.equals("Double")) {
sqlBuilder.append(condition[i].second.toString());
}
}
return sqlBuilder.toString();
}
@Override
public int insert(Connection conn, PreparedStatement pstmt,
String database, List<String> columns, List<List<Object>> lists) throws SQLException {
// 如果传入的列名不正确, 或者
if (columns.size() <= 0 || lists.size() <= 0) {
throw new RuntimeException();
}
String sql = "INSERT INTO ?(" + appendColumns(columns) + ") VALUE";
// 添加所有values
StringBuilder sqlBuilder = new StringBuilder(sql);
for (int i = 0; i < lists.size(); i ++ ) {
// 如果值的数量不匹配
if (lists.get(i).size() != columns.size()) {
throw new RuntimeException();
}
// 除第一个外, 所有value在前面添加","
if (i != 0) sqlBuilder.append(", ");
// 拼接第i个value
sqlBuilder.append(appendValues(lists.get(i)));
}
sql = sqlBuilder.toString();
// 设置数据库名
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, database);
// 执行更新
pstmt.executeUpdate();
return 0;
}
private String appendColumns(List<String> columns) {
StringBuilder ans = new StringBuilder("(");
for (int i = 0; i < columns.size(); i ++ ) {
if (i != 0) ans.append(", ");
ans.append(columns);
}
ans.append(")");
return ans.toString();
}
private String appendValues(List<Object> list) {
StringBuilder ans = new StringBuilder("(");
for (int i = 0; i < list.size(); i ++ ) {
if (i != 0) ans.append(", ");
String className = list.get(i).getClass().getName();
// 只判断了传入的字符串和数字
if (className == "String") {
ans.append("'").append(list.get(i)).append("'");
}
else if (className.equals("Integer")) {
ans.append(list.get(i).toString());
}
else if (className.equals("Double")) {
ans.append(list.get(i).toString());
}
}
ans.append(")");
return ans.toString();
}
}
ObjectDao
package dao;
import jdk.internal.net.http.common.Pair;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
public interface ObjectDao {
/**
* 查询数据库总方法
*
* @param columns 查询的列名称集合, 以逗号分割或者*
* @param database 数据库名
* @param condition 条件集, 以Pair<String, Object>键值对的形式传入
* @return 返回结果集
*/
ResultSet select(Connection conn, PreparedStatement pstmt,
String columns, String database, Pair<String, Object>... condition) throws SQLException;
/**
* 插入数据库总方法
*
* @param database 数据库名
* @param columns 插入列的名称
* @param list 数据对象集
* @return 插入成功的数量
*/
int insert(Connection conn, PreparedStatement pstmt,
String database, List<String> columns, List<List<Object>> list) throws SQLException;
/**
* 删除总方法
*
* @param database 数据库名
* @param condition 条件集, 以Pair<String, Object>键值对的形式传入
*/
void delete(Connection conn, PreparedStatement pstmt,
String database, Pair<String, Object>... condition) throws SQLException;
}
经过书写, 基本操作已经封装. 但是程序的复用性及变量类型是否满足要求还有待进一步考证. 欢迎各位反应Bug.
组长你咋啥都会呀,这不带带我