一段头疼的代码
代码段
public class DAO<T>{
private Class<T> clazz = null;
{
Type type = this.getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] types = parameterizedType.getActualTypeArguments();
this.clazz = (Class<T>) types[0];
}
// ....
}
// 1. 获取子类的原型对象
Class<? extends BaseDAO> childClass = this.getClass();
// 2. 获取当前子类的直接父类的类型
Type type = childClass.getGenericSuperclass();
// 3.获取父类泛型的参数化类型
ParameterizedType parameterizedType = (ParameterizedType) type;
// 4. 获取所有参数对象
Type[] types = parameterizedType.getActualTypeArguments();
// 5.获得第一个参数对象 并赋值给 cls
this.cls = (Class<T>)types[0];
今天在学习 DAO 的优化的时候,这一段代码让我很头痛,经过我的一番找博客,敲代码验证,功夫不负有心人,终于被我弄懂了
写这段代码的思考过程是这样的:
1.0 版本 DAO 类
1. 考虑事务 使用同一个连接。
2. 方法涉及
update() 可以执行增删改,影响数据库中的数据
getInstance() 执行查询获取单个记录对象
getForList() 执行查询获取多个记录对象的集合
getValue() 可以返回分组函数的结果
2.0 版本 DAO 类 加泛型 优化
思考过程
- 在获取单条记录时,
CustomerDAOImPl
中getCustomerById()
调用getInstance(Connection conn,Class<T> clazz,String sql,Object ...args)
- 在获取多条记录时,
getAll()
的内部 调用getForList(Connection conn,Class<T> clazz,String sql,Object ...args)
- 他们都在调用时 传入了
Customer.class
==>Customer的原型类对象来指明返回什么表的bean 对象
在内部都使用了这个传入的泛型的指定类型的原型类通过反射封装bean 对象和返回bean对象或者是集合
在调用时都需要传入 XXX.class 去指明表这个过程显得很重复 所以这里是可以优化的地方
优化方案
-
BaseDAO 类 ==> 声明成泛型类 BaseDAO
在实现类继承DAO的时候指定泛型参数的类型CustomerDAOImpl extends BaseDAO<Customer> implement CustomerDAO
-
BaseDAO 类的所有泛型方法 ==> 使用BaseDAO 定义的泛型,不声明成泛型方法,方法的形参就不需要传入指定的 XXX.class 用于指定泛型参数
-
在BaseDAO中声明泛型参数的属性并使用泛型参数的原型对象进行初始化,所有需要使用到泛型参数的地方引用同一个泛型参数进行反射调用
-
泛型参数的定义
private Class<T> clazz = null;
-
泛型参数的初始化:
- 直接初始化
- 代码块中初始化
- 构造器中初始化
-
为什么泛型参数需要初始化?
-
分析:
如果泛型参数不初始化,在 XXXDAOImpl 创建对象并调用 getInstanceByXX()
方法内部会调用 getInstance(),就会使用这个泛型参数的原型对象来反射使用,未初始化,就会报空指针异常
泛型参数如何初始化? -
分析:
上述的问题导致空指针异常,所以需要在创建对象之前或者是创建对象的时候要去调用这个getInstanceByXX()
需要保证我这个泛型参数的引用已经被原型对象初始化了,就是在创建子类对象之前即可 -
分析结果
问题解决的突破口:类加载机制的原理
创建XXXDAOImpl 对象会导致类加载,而且类是一级一级被加载
查阅笔记:
创建一个子类的继承关系时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造器的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法
说明:创建子类对象时,调用子类构造器,默认就隐含 调用父类的构造器super() 和 父类的普通代码块和普通属性的执 行。
创建子类对象 要进行类加载 所以先加载父类,若存在多级继承关系,则从最上层父类开始加载父类至子类,类加载必然会执行静态代码块和静态属性,他们优先级一样,看谁在前面,所以的类加载完毕后,再按照 普通代码块,属性,然后再构造器去执行。
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();
/*
执行顺序是:(1)父类静态相关代码块、属性初始化(2)子类静态相关代码块、属性初始化
(3)父类的普通代码块、属性初始化(4)父类的构造器初始化(5)子类的普通代码块、属性初始化
(6)子类的构造器初始化
MMM的静态代码块被执行
AAA的静态代码块被执行
AAA的静态属性被执行
BBB的静态代码块被执行
hobby普通属性
MMM的普通代码块
MMM的构造器被执行
AAA的普通属性被执行
AAA的普通代码块被执行
AAA的构造方法被执行
BBB的普通代码块被执行
BBB的构造方法被执行
*/
}
}
class MMM{
// MMM的普通属性和普通方法
public String hobby = getHobby();
public String getHobby(){
System.out.println("hobby普通属性");
return "hobby";
}
static{
System.out.println("MMM的静态代码块被执行");
}
public MMM(){
System.out.println("MMM的构造器被执行");
}
{
System.out.println("MMM的普通代码块");
}
}
class AAA extends MMM {
// 静态代码块
static {
System.out.println("AAA的静态代码块被执行");
}
public static int age = getAge();
public static int getAge() {
System.out.println("AAA的静态属性被执行");
return 100;
}
public String name = getName();
{
System.out.println("AAA的普通代码块被执行");
}
public String getName() {
System.out.println("AAA的普通属性被执行");
return "hhh";
}
public AAA() {
System.out.println("AAA的构造方法被执行");
}
}
class BBB extends AAA {
{
System.out.println("BBB的普通代码块被执行");
}
static {
System.out.println("BBB的静态代码块被执行");
}
public BBB() {
// 隐含调用父类的构造器
// super()
// 本类的普通代码块被执行
System.out.println("BBB的构造方法被执行");
}
}
查阅csdn
原来卡在这
👉 点击跳转 ParameterizedType详解 https://blog.youkuaiyun.com/candyguy242/article/details/102940035
理解代码并代码验证
class Boy{
}
class Girl{
}
class A<T,U>{
// 定义泛型参数
private Class<T> type = null;
// 在代码块中初始化泛型参数
{ // 获取子类的原型对象
Class cls = this.getClass();
System.out.println("当前对象:"+this.getClass());
// 获取当前子类的直接父类的类型
// Type接口对象必须准确反映源代码中使用的实际类型参数
Type gsc = cls.getGenericSuperclass();
System.out.println(gsc); // com.zq.dao.A<com.zq.dao.Boy, com.zq.dao.Girl>
// 获取父类泛型的 参数化类型
ParameterizedType parameterizedType = (ParameterizedType) gsc;
// 获取父类泛型参数化类型 的所有 参数对象
Type[] types = parameterizedType.getActualTypeArguments();
// 获取参数对象
Type type1 = types[0];
// class com.zq.dao.Boy 地址:21282042
System.out.println("泛型的参数对象:"+type1+"地址:"+type1.hashCode());
// 初始化赋值
this.type = (Class<T>) type1;
}
}
class B extends A<Boy,Girl>{
B(){
}
public static void main(String[] args) {
new B();
}
}
当前对象:class com.zq.dao.B
com.zq.dao.A<com.zq.dao.Boy, com.zq.dao.Girl>
泛型的参数对象:class com.zq.dao.Boy地址:21282042
豁然开朗
所以子类创建对象,导致的类一级一级的加载,根据类加载顺序,使得这个原型对象在子类创建对象之前初始化即可
private Class<T> type;
// 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
public BaseDao() {
// 获取子类的类型,
// 子类创建对象,调用构造方法,导致类加载,根据类加载原则,
// 父类构造器比子类构造器先加载,并且目前的对象是子类对象,this的运行类型就是子类对象,获取的也就是
// 子类的Class 对象
Class clazz = this.getClass();
// 获取父类的类型
// getGenericSuperclass()用来获取当前类的父类的类型
// ParameterizedType表示的是带泛型的参数化类型==> 获取的是 class A<T,E,F> 像这样尖括号中有参数的泛型
ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
// 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的参数类型:通俗的讲就是获得 <A,B> 获得尖括号中的东西
// 这个方法会返回一个Type的数组==> 尖括号中的东西不止一个
Type[] types = parameterizedType.getActualTypeArguments();
// 获取具体的泛型的类型的对象并赋值给 type ,让 type 初始化,后面每个方法就可以使用这个 type 来通过反射来创建对象,封装数据返回s
this.type = (Class<T>) types[0];