泛型只在编译时期有效,编译后的字节码文件中不存在泛型信息,这一点对于我们判断泛型使用规范很有帮助。例如
通常情况下,方法中接受的参数列表如果被写为
public void save(List<? extends Number> list){
System.out.println(list.get(0));
}
就代表,传入的List集合中元素,必须是数字包装类,例如Integer,Double,Float等—Number类型规定了List结合中类型的上限。因此,如果我们调用该方法时使用
//1#
List<String> list = new ArrayList<String>();
list.add("BJTShang")
save(list);
或者
//2#
List<String> list = new ArrayList();
list.add("BJTShang")
save(list);
则必然报错。
但是,如果我们的调用代码是这样的:
//3#
List list = new ArrayList<String>();
list.add("BJTShang");
save(list);
或者是这样的:
//4##
List list = new ArrayList();
list.add("BJTShang");
save(list);
就只会提示类型警告,而不会报错,也可以正常打印出BJTShang。
原因就是泛型擦除:
泛型只在编译时期有效,编译后的字节码文件中不存在泛型信息。
3#和4#代码能够正常运行并得出结果的原因就在于,在编译时期,只检查引用类型list,因此编译通过。虽然实际上传入的类型是不符合的,但是在编译后的字节码文件中是没有泛型信息的,因此此时已经没有泛型约束。相当于普通的List集合类。
在案例中设置通用方法,会用到反射泛型!
案例分析,涉及的API,演示优化
修改数据库字段名字或者类型:
ALTER TABLE account CHANGE COLUMN id newid VARCHAR(20);
注意,有外键关联的字段,不能修改!
Type接口是所有Java类型的默认接口:
包括:引用类型;原始类型(基本数据类型);参数化类型,ParameterizedType,也就是JDK1.5之后引入的泛型,例如:”ArrayList”
通过泛型的反射,可以获得参数化类型
MVC模式中,模型层的Dao,通常需要处理多种数据,例如,通过继承泛型父类BaseDao,在实现时,指定泛型T的类型(AdminBaseDao extends BaseDao)。但是,由于数据操作通常是类似的,希望把处理数据的通用方法(例如T findById(int id))抽取到父类BaseDao中处理,那么就需要在BaseDao类中知道子类封装的对象类型,因此需要通过参数化类型的反射获取。
为了方便获取结果集,引入Dbutils技术:导入commons-dbutils.jar包,如果是Java项目,在项目根目录下新建一个lib文件夹放入后build path;如果是JavaWeb项目,将jar包放入WEB-INF文件夹下的lib文件夹中再build path。
在src目录下配置c3p0-config.xml文件:
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day27</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">10</property>
<property name="maxPoolSize">25</property>
<property name="maxIdleTime">3000</property>
</default-config>
</c3p0-config>
编写JdbcUtils类:
package com.cityu.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
/**
* Jdbc工具类,需要使用c3p0和dbutils技术
* @author shangxu2-c
*
*/
public class JdbcUtils {
private static ComboPooledDataSource ds = null;
static{
//使用类时初始化,根据c3p0-config.xml配置文件
//1. 加载数据库驱动
//2. 根据通信协议获得数据库Url
//3. 根据用户名密码登陆数据库并取得连接池配置
//4. 拿到数据源,其中包含了所有的数据库连接池配置信息
ds = new ComboPooledDataSource();
}
public static Connection getConnection() throws Exception {
return ds.getConnection();
}
//dbutils工具方法,封装数据库操作方法
public static QueryRunner getQueryRunner(){
return new QueryRunner(ds);
}
/*public static void release(Connection conn,Statement st,ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (st!=null) {
try {
st.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}*/
}
首先,实体类名必须和数据库表名一致,这样,通过泛型中的参数化类型才可以可以获得数据库表名,用于sql语句;
package com.cityu.basedao;
//实体类名必须对应数据库中表名(数据库中不区分大小写)
public class Admin {
private int id;
private String username;
private String pwd;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "Admin [id=" + id + ", username=" + username + ", pwd=" + pwd
+ "]";
}
}
接下来,实体类的Dao类直接继承泛型基类BaseDao,通过泛型向上抽取统一方法。这样,对于不同的实体类,均可以通过BaseDao具体实现操作数据库。
public class AdminDao extends BaseDao<Admin> {
}
最后是编写BaseDao泛型类
package com.cityu.basedao;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import com.cityu.utils.JdbcUtils;
//实体类型通过T传入
public class BaseDao<T> {
private Class clazz;
private String tableName;
public BaseDao(){
//获取参数类型
ParameterizedType type
= (ParameterizedType) this.getClass().getGenericSuperclass();
Type[] types = type.getActualTypeArguments();
clazz = (Class) types[0];
//获取数据库表名
tableName = clazz.getSimpleName();//getName()方法获取类全名
}
public T findById(int id){
String sql;
try {
sql = "SELECT * FROM "+tableName+" WHERE id = ?";
return JdbcUtils.getQueryRunner().query(sql, new BeanHandler<T>(clazz), id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在测试类或者service层中,直接调用BaseDao中的方法,即可完成对多个实体类的操作
package com.cityu.utils;
import org.junit.Test;
import com.cityu.basedao.Admin;
import com.cityu.basedao.AdminDao;
public class TestApp {
@Test
public void test() throws Exception {
//在service层中,通常使用工厂方法创建实体Dao
AdminDao adminDao = new AdminDao();
Admin admin = adminDao.findById(2);
System.out.println(admin);
}
}