父类通过泛型获得子类Class类型 以及Type体系

1、背景介绍

在实现SSH框架中,DAO层向数据库持久化的过程中,因为大部分保存对象的方法都会调用到sava();所有索性就把save delete update select 方法进行封装到父类中,这时候就遇到了个问题,子类在调用这些方法的时候,需要根据子类的类型获知子类Class类型;这个时候可以通过传入泛型,根据泛型的类型来获取子类的Class类型; 

2、实现代码范例

父类:public abstract class Parents<E>

{

  private Class<?> child;

public Parents()
    {
         Class<?> c = this.getClass(); //子类创建 会创建父类 子类调用时 此处的this是子类
         Type t = c.getGenericSuperclass(); //获得带有泛型的父类
         if (t instanceof ParameterizedType)
         {
              Type[] p = ((ParameterizedType) t).getActualTypeArguments();  //取得所有泛型
              this.child= (Class<E>) p[0];
         }

}

  在子类调用父类的方法时,如果父类的方法中需要知道具体子类的Class类型 则可以直接使用Child来使用;

  此处的原理就是在子类继承父类的时候 带有泛型 然后子类在创建的时候,会调用父类的构造函数,构造函数中存在this指的的是子类,然后通过获得父类,再获得父类的泛型

;通过泛型找到子类类型;

3、原理分析

  该实现是通过反射技术实现;下面看具体的分析;

3、1 ParameterizedType 类

ParameterizedType,参数化类型,形如:Object<T, K>,即常说的泛型,是Type的子接口。

public interface ParameterizedType extends Type {
    //1.获得<>中实际类型
    Type[] getActualTypeArguments();
    //2.获得<>前面实际类型
    Type getRawType();
    //3.如果这个类型是某个类型所属,获得这个所有者类型,否则返回null
    Type getOwnerType();
}

1.getActualTypeArguments 
获得参数化类型中<>里的类型参数的类型,因为可能有多个类型参数,例如Map<K, V>,所以返回的是一个Type[]数组。 
注意:无论<>中有几层<>嵌套,这个方法仅仅脱去最外层的<>,之后剩下的内容就作为这个方法的返回值,所以其返回值类型不一定。 
例如:

1. List<ArrayList> a1;//这里返回的是,ArrayList,Class类型
2. List<ArrayList<String>> a2;//这里返回的是ArrayList<String>,ParameterizedType类型  可以继续通过调用getActualTypeArguments获得其泛型类型
3. List<T> a3;//返回的是T,TypeVariable类型
4. List<? extends Number> a4; //返回的是WildcardType类型
5. List<ArrayList<String>[]> a5;//GenericArrayType
要注意,ArrayList与ArrayList<String>的不同。
public static void main(String[] args) throws Exception
    {
        Method method = new Main().getClass().getMethod("test", List.class);//这里的第二个参数,和getRawType()意义类似
        Type[] types = method.getGenericParameterTypes();
        ParameterizedType pType = (ParameterizedType) types[0];
        Type type = pType.getActualTypeArguments()[0];
        System.out.println(type);
        //type是Type类型,但直接输出的不是具体Type的五种子类型,而是这五种子类型以及WildcardType具体表现形式
        System.out.println(type.getClass().getName());
    }
    public void test(List<ArrayList<String>[]> a)   {
    }

2.getRawType 
返回最外层<>前面那个类型,即Map<K ,V>的Map

Map<Integer, String> maps = new HashMap<>();
ParameterizedType pType = (ParameterizedType) maps.getClass().getGenericSuperclass();//获得HashMap的父类
System.out.println(pType.getRawType());//class java.util.AbstractMap
if(pType.getRawType() instanceof Class){
      System.out.println("true");//true
}
//注意类型(Type)与类(Class)的区别

3、2 Type类

  Type是java类型信息体系中的顶级接口,其中Class就是Type的一个直接实现类。此外,Type还有有四个直接子接口:ParameterizedType,TypeVariable,WildcardType,GenericArrayType。

引用这位仁兄对这几个接口的介绍[转载]:

Type
      它是所有类型的公共接口。包括原始类型、参数化类型、数组类型、类型变量和基本类型。ParameterizedType, TypeVariable, WildcardType,GenericArrayType这四个接口都是它的子接口。

3、2、1 GenericDeclaration

  这个接口Class、Method、Constructor都有实现,我们就是要用这个接口的getTypeParameters方法,它返回一个TypeVariable[]数组,这个数组里面就是我们定义的类型变量T和K,顺序与我们声明时一样。如果用循环语句将数组打印出来,你会发现只会输出T和K,这可不是我们想要的结果,那么想要获得预期的结果怎么办呢?请继续往下看。

3、2、2 TypeVariable

  它表示类型变量。比如T,比如K extends Comparable<? super T> & Serializable,这个接口里面有个getBounds()方法,它用来获得类型变量上限的Type数组,如果没有定义上限,则默认设定上限为Object,请注意TypeVariable是接口,实际得到的是TypeVariableImpl实现类,下面几个接口都一样。
    拿T和K来说明,T没有定义任何上限,所以它就有一个默认上限java.lang.Object,实际跟踪代码的时候你会发现T的bounds属性为空,只有在调用了getBounds()方法后,才会有一个Type[1]数组[class java.lang.Object]。而对于K来说,调用了getBounds方法后,得到的数组是[java.lang.Comparable<? super T>, interface java.io.Serializable],它们的类型却是不一样的,第1个是ParameterizedType,而第二个是Class
    

3、2、3 ParameterizedType

  ParameterizedType表示参数化类型,就是上面说的java.lang.Comparable<? super T>,再比如List<T>,List<String>,这些都叫参数化类型。得到Comparable<? super T>之后,再调用getRawType()与getActualTypeArguments()两个方法,就可以得到声明此参数化类型的类(java.lang.Comparable)和实际的类型参数数组([? super T]),而这个? super T又是一个WildcardType类型。

3、2、4 WildcardType

它用来描述通配符表达式,上面返回的? super T正好是这个类型。然后调用getUpperBounds()上限和getLowerBounds()下限这两个方法,获得类型变量?的限定类型(上下限),对于本例的通配符(?),它的上限为java.lang.Object,下限为T
通过上面几个接口的分析,可以将Person类的泛型参数都解析出来,那么Person的超类以及实现的接口该怎么处理呢?Class类里面同样在1.5版本加入了getGenericSuperclass()与getGenericInterfaces()两个方法,用于返回带参数化类型的超类与接口。

3、2、5 GenericArrayType其实就是泛型数组类型。

  我们说Class在一定程度上挽救了擦除的类型信息,我们就可以通过这几个接口来获取被擦除的类型参数信息,这几个接口无非就是对类型参数的一个分类,通过它们提供的一些方法,我们可以逐步的获取到最原始的类型参数的Class对象。

  具体的说明和API大家可以去看文档,我这里记录一个实际的应用,当然在各种框架中的应用比比皆是。

  在JavaEE的Dao层我们一般都会封装出一个通用的泛型BaseDao,它可以实现对各种实体例如User,Order的基本CRUD,然后具体的UserDao,OrderDao等等会去继承它,提供其他的Dao方法:

public class UserDao extends BaseDao<User>{}

我使用的BaseDao是基于DBUtils的,它需要实体的Class对象才能进行通用的查询方法,例如User的Class对象,我们可以通过构造函数,函数参数等手段传递给BaseDao,但是有了反射,可以有更优雅的实现。
public class BaseDao<T> {

    private Class<T> clszz;

    public BaseDao(){
        Type type = this.getClass().getGenericSuperclass();//拿到带类型参数的泛型父类
        if(type instanceof ParameterizedType){//这个Type对象根据泛型声明,就有可能是4中接口之一,如果它是BaseDao<User>这种形式
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//获取泛型的类型参数数组
            if(actualTypeArguments != null && actualTypeArguments.length == 1){
                if(actualTypeArguments[0] instanceof Class){//类型参数也有可能不是Class类型
                    this.clszz = (Class<T>) actualTypeArguments[0];
                }else{
                    //例如: BaseDao<BaseDao<User>>,获取到的就不是Class,而又是ParameterizedType,即嵌套的
                    ParameterizedType,一层一层剥开,最终是可以得到User的Class对象的
                }
            }
        }
    }

    public T get(String sql,Object...params){
        QueryRunner qr = new QueryRunner();
        T obj;
        Connection connection;
        try {
            connection = JdbcUtil.getConnection();
            obj = qr.query(connection,sql,new BeanHandler<T>(clszz),params);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
        return obj;
    }
}

原文地址:https://www.cnblogs.com/gxyandwmm/p/9382579.html

时间: 2024-08-29 19:48:01

父类通过泛型获得子类Class类型 以及Type体系的相关文章

协变、逆变与不变:数组、泛型、与返回类型

转自:http://blog.csdn.net/yi_Afly/article/details/52071260 1. 前言 之前几篇博文,有些地方涉及到了协变性.逆变性与不变性在Java中的表现,所以这篇博文将重点记录这方面的内容,并辅以JDK源码中的一些实例,加以说明. 2. 定义 这里讨论的协变.逆变与不变都是编程语言中的概念.下面介绍定义: 若类A是类B的子类,则记作A ≦ B.设有变换f(),若: 当A ≦ B时,有f(A)≦ f(B),则称变换f()具有协变性. 当A ≦ B时,有f

从头认识java-13.11 对比数组与泛型容器,观察类型擦除给泛型容器带来什么问题?

这一章节我们继续类型擦除的话题,我们将通过对比数组与泛型容器,观察类型擦除给泛型容器带来什么问题? 1.数组 package com.ray.ch13; public class Test { public static void main(String[] args) { Fruit[] fruits = new Apple[5]; fruits[0] = new Apple(); fruits[1] = new Fuji(); fruits[2] = new Fruit(); } } cla

从头认识java-13.11 对照数组与泛型容器,观察类型擦除给泛型容器带来什么问题?

这一章节我们继续类型擦除的话题,我们将通过对照数组与泛型容器,观察类型擦除给泛型容器带来什么问题? 1.数组 package com.ray.ch13; public class Test { public static void main(String[] args) { Fruit[] fruits = new Apple[5]; fruits[0] = new Apple(); fruits[1] = new Fuji(); fruits[2] = new Fruit(); } } cla

关于父类私有属性在子类构造函数中super调用的解释

package test; public class Car { private int carMoney; //汽车租金 private String carName; //汽车名字 private int carNumb; //汽车序列号 private int carryNum; //载货量 private int passengerNum;//载客人数 public Car(int carNumb,String carName,int carMoney,int passengerNum,

java 通过反射获取类属性结构,类方法,类父类及其泛型,类,接口和包

首先自定义三个类 package reflection1; public interface MtInterface { void info(); } package reflection1; import java.io.Serializable; public class Creature<T> implements Serializable { private char gender; public double weight; private void breath() { Syste

泛型的几种类型以及初识webform

今天学习的可以分为两类吧,但是学习的都是比较抽象的,不太容易掌握吧.首先我们大部分时间学习了泛型,泛型的委托,泛型接口以及枚举器,迭代器,扩展方法:最后简单的认识了webform,实现了一个简单的功能. 一.泛型 定义:泛型(generic)可以软糖多个类型共享一组代码,泛型允许我们声明类型参数化.可以用不同的类型进行实例化,说白了,就是可以用类型占位符,创建具体类型致命的真实概念.C#中提供了五种泛型,类,结构,接口,委托和方法.下面举例说明可能更容易理解, class MyStack<T>

不可见类有抽象父类,spring配置子类bean,注入父类,aop就可以切父类的方法

public class TestBeanChild { int b = 1; public TestBean createDefault() { return new TestBeanDefault(); } } class TestBeanDefault extends TestBean{ int b=2; } 包外面不能访问类TestBeanDefault public abstract class TestBean { private int a =0; public void doBe

父类的引用指向子类的对象或者接口指向实现类均是可以的

父类的引用指向子类的对象或者接口指向实现类均是可以的. 例如(下图为父类的引用指向子类的对象) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Father {         public void pri() {             System.out.println("father");         } } public class Sun   extends  Father{       public void 

Java子类继承父类,当实例化子类对象时的执行顺序

子类继承父类,当实例化子类对象时,执行顺序如下:     父类层级静态块      自身静态块     父类块      父类构造器     自身块      自身构造器 由于继承,肯定应该先加载父类再加载子类,加载类自然会调用静态块,然后是调用父类的构造函数和子类的构造函数,不过构造函数调用前肯定要先调用域或块,这样才能正确创建对象. ················································赋值顺序如下:     父类的静态变量赋值     自身的静态变