Java反射与泛型

参考:https://mp.weixin.qq.com/s?src=11&timestamp=1578106730&ver=2075&signature=fs-cx1MPWijVQCnuEYuzOeQRiOJjfVL*cD8yG6mahPqTQVricIRFQnVIm5yRcp9SlPTXuk0AZrTNiS6Solle8QNoNeNQvNAuWsiMzf7Pakt6ptdlfahxjVYZ5oFlF*Bk&new=1

https://mp.weixin.qq.com/s?src=11&timestamp=1578104847&ver=2075&signature=qegT7xFizpbsFtyBT7L9ec77dpxgbVm-A6hl-jyqTwQVRHebujcxf4SHLo3Q1KhNOr-MoBwG3KvH4j7uisoRWIlUHr6a9CiLm2IGQknwms4xMHqzErQo1PLDApK2khPR&new=1

https://www.jb51.net/article/103475.htm

https://www.cnblogs.com/rgogoing/p/5325196.html

https://www.jb51.net/article/115351.htm

一、泛型

在日常编程的过程中,泛型在这三个特性之中使用频率是最高的。”泛型”一词中的泛字可以理解为泛化的意思,即由具体的、个别的扩大为一般的。Oracle对泛型的官方定义是:泛型类型是通过类型参数化的泛型类或接口。一言以蔽之,泛型就是通过类型参数化,来解决程序的通用性设计和实现的若干问题。

Java泛型是1.5版本后引入的特性,它主要被用于解决三类问题:

1、编译器类型检查

假设有两个List对象,一个用于传入String类型数据的list1,一个用于传入Int类型数据的list2,但是在实际使用过程中,有可能往list1中传入Int类型数据,导致运行时错误,但是编译时却没有问题。

在Java5之后,使用List<T>就可以解决这种问题,这个声明的背后实质是引入了可以在类中任何地方使用的类型变量T。

List<T>可以理解为“在使用泛型时,可以将类型参数T传递给List类型本身”,结合Oracle给出的官方定义“泛型的本质是类型参数化”会更有理解。

通过对于泛型的使用,之前的多业务场景中的问题都得到了解决,因为现在在编译阶段就可以解决类型不匹配的问题,而不用等到运行时才暴露问题,只要合理使用泛型,就能在很大程度上规避此类风险。对于泛型的使用,这种参数化类型的作用表面上是声明,背后其实是约定。

2、强制类型转换

在使用泛型之后,解决了某些场景下必须进行强制类型转换的问题。通过泛型声明,指定了集合内元素的类型参数,这样编译器就直接知晓了元素的类型,而无需依靠实际的业务逻辑进行转换,从而解决了类型强制转换的问题。

3、可读性和灵活性

泛型除了能进行编译器类型检查和规避类型强制转换外,还能有效地提高代码的可读性。在实际场景中,如果不使用泛型,当一个不清楚业务场景的人在对集合进行操作时,无法知道list中存储的是什么类型的对象,如果使用了泛型,就能够通过其类型参数判断出当前的业务场景,也增加了代码的可读性,同时也可以大胆地在抽象继承的基础之上开发。

泛型使用上的灵活性体现在很多方面,因为它本身实质上就是对于继承在使用上的一种增强。因为泛型在具体工作时,当编译器在编译源码的时候,首先要进行泛型类型参数的检查,检查出类型不匹配等问题,然后进行类型擦除并同时在类型参数出现的位置插入强制转换指令,从而实现泛型。

除了上述的基础用法之外,泛型还有几种特殊的高阶用法:

通配符的设计存在一定的场景,例如在使用泛型之后,首先声明一个Animal的类,而后声明了一个继承自Animal类的Cat类,显然Cat类是Animal类的子类,但是List<Cat>却不是List<Animal>的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法,具体来说有三种用法:<? extends T>、<? super T>、<?>。其中前两者被称为限定通配符,<?>被称为非限定通配符。

1、<? extends T> 上界通配符

 

上界通配符顾名思义,<? extends T>表示的是类型的上界(包含自身),因此通配的参数化类型可能是T或T的子类。正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List<Animal>和List<Cat>是List<? extends Animal>的子类型。

 

2、<? super T> 下界通配符

 

下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object,编译器无从判断get()返回的对象的类型是什么,因此get()方法受限。但是可以进行add()方法,add()方法可以添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,然后添加了两个Cat子类类型的对象,这种方法是可行的,但是如果添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。

 

3、<?> 无界通配符

 

在理解了上界通配符和下界通配符之后,其实也自然而然的理解了无界通配符。无界通配符用<?>表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List<Object>和List<null>的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)。

 

二、反射机制

反射是Java语言本身具备的一个重要的动态机制,用一句话来解释反射的定义:自控制,自描述。即通过反射可以动态地获取类,属性,方法的信息,也能构造对象并控制对象的属性和行为。

假设有一个Apple类,它有两个构造器、一个属性和get()、set()两个行为。在左侧的“自描述”中主要是尝试在动态的过程中借助反射获取Apple类的构造器信息和对应的参数个数、类的属性信息和类的方法信息。其中有一个Class类型,它可以产生Class对象被ClassLoader加载,从而在jvm中实现对它的调用。在这段程序中,打印了一些类的信息、类的属性信息和类的方法信息。在右侧的“自控制”的代码中,实现了在运行的过程中创建了一些对象并触发这个对象的一些行为,最后还尝试对对象的属性进行赋值。反射的基本使用方法较为简单,但是这种机制却增强了Java语言的灵活性。

非反射的Java类的大致运行流程是:编写源文件Apple.java,然后编译器将其编译成字节码文件Apple.class,最后加载到jvm中并运行。而采用反射的方式时,编译器一开始对其类型(编译类型和动态类型)是一无所知的,只有在运行过后,编译器才知道其真正的类型。

 

反射的优势主要在于两点:

 

  1. 在一些场景中,这种“未知类型”实际上大大增强了程序运行时的灵活性,但是其性能会有一些损耗;

  2. 对于对象的类型可以在运行时判断,这样的特性实质上是对多态极大地增强,进一步地将上层的抽象与下层的具体实现进行解耦。

 

这两点在JDBC Driver中体现的非常明显,例如上图中的实例中,JDBC的驱动加载方式是通过反射机制实现的,从而保证运行时可以动态选择要加载的驱动程序,程序灵活性大大增强。另外,JDBC只是设计了驱动需要实现的接口,并不关心驱动厂商的个数和实现方式,只要安装统一的规范即可,至于类型的判断和具体方法的触发,交给运行期动态判断即可,这种反射机制的使用淋漓尽致的体现了多态,并且降低了类与类之间的耦合度。

 

反射使用用在哪?

首先我们先思考一个问题,反射适合使用在哪里呢?从功能上看,反射似乎无所不能,几乎所有的类,所有的属性、方法、构造我们都能使用,但是我们细细思考一下,在实际开发中,并不是所有场景都需要使用反射获取属性或者方法进行操作,反而更多的使用实例.xxx方式操作,而当这些操作重复次数较多的时候,我们往往会考虑优化代码,减少代码冗余,提高复用,比如实体构建赋值等操作,这个时候往往是我们最需要复用的地方,所以我们可以大体认为反射主要使用在实体操作过程中。而在一般操作数据的过程中,我们的实体一般都是知道并且依赖于对应的数据类型的,比如:

1.根据类型new的方式创建对象

2.根据类型定义变量,类型可能是基本类型也可能是引用类型、类或者接口

3.将对应类型的对象传递给方法

4.根据类型访问对象的属性,调用方法等操作

以上这些操作都是数据操作的过程中最常见也是最难复用优化的地方,而如果这里的操作使用反射则可以实现动态的操作不同的类型的实例,通过调用反射入口类Class,获取对应的属性、构造、方法完成对应的操作,所以接下来我们先从Class入口开始学习。

获取Class

学习过类和继承实现原理的时候,我们都知道,每个已经加载的类在内存中都有一份对应的类信息,而每个对象都有所属类的引用。其中类信息存放的类即为java.lang.Class。而所有类的基类Object中就有一个本地方法可以快速获取到Class对象。

 
  1. publicfinalnativeClass<?> getClass()

可以看到返回的类型为Class,即为当前类的信息,但是由于基类的子类并不明确,所以具体的类型这里使用范型的方式返回。

而getClass方法不仅仅可以使用在类中,针对于接口也可以使用,当然接口没有具体的实现,所以不可能是接口的实例.getClass的方式获取,这个时候我们就需要调用内置的.class属性快速获取类型:

 
  1. Class<Comparable> cls = Comparable.class;

而在java中有一些特殊的类型,例如基本类型,还有void类型,这种类型无法直接创建实例,如果要获取class,与接口的方式相同,如下:

 
  1. //这里可以看出来,基本类型的class即为对应的包装类型

  2. Class<Integer> intCls = int.class;

  3. Class<Byte> byteCls = byte.class;

  4. Class<Character> charCls = char.class;

  5. Class<Double> doubleCls = double.class;

  6. //void也是一种特殊的类型,返回的也是对应的包装类Void

  7. Class<Void> voidCls = void.class;

而数组和枚举作为java中的特殊实现类,获取的class类型也是较为特殊的,数组具有维度特性,所以获取的class类型同样具有维度,即一维数组有一个,二维数组有两个,每个维度都有对应的不同类型,而枚举的class则是其中定义的每一个子集,如下:

 
  1. String[] strArr = newString[10];

  2. int[][] twoDimArr = newint[3][2];

  3. int[] oneDimArr = newint[10];

  4. Class<? extendsString[]> strArrCls = strArr.getClass();

  5. Class<? extendsint[][]> twoDimArrCls = twoDimArr.getClass();

  6. Class<? extendsint[]> oneDimArrCls = oneDimArr.getClass();

而通过上述方式获取Class对象以后,我们就可以了解到关于类型的很多信息,并且基于这些信息可以获取我们想要的详细信息,大致可以分为以下几类,包括名称信息、字段信息、方法信息、创建对象的方法、构造信息与类型信息等,接下来我们就分别学习这几类信息相关的内容。

Class名称信息

我们在开发过程中,获取到Class以后,往往需要获取对应的类名进行操作,比如比较类名,排除类名等操作,而在Class类中,提供了以下几个方法获取类名称相关信息:

 
  1. publicString getName()

  2. publicString getSimpleName()

  3. publicString getCanonicalName()

  4. publicPackage getPackage()

这里可以看出分别能获取四种不同的类名称,那么获取的具体内容有什么不同呢?我们先看一张罗列的表格信息:

可以看出来各个不同类型的Class通过四种方法获取的类名称信息完全不同,那么为什么会出现这么大的区别呢?这里我们需要注意的是:

getName()

getName()方法获取的是标准的类名称信息,即Java内部真正的类型对应的名称信息(JVM真实的类名称信息),这里我们可以看出来数组的getName()的结果为[I,这里需要解释一下,[表示的是数组,并且和维度有关系,如果是二维数组,那么这里就会是[[,而后面的I则是int类型在JAVA中真实的类型的简写,八大基本类型对应的类简写名称如下所示:

基本类型真实类名称简写
booleanZ
byteB
charC
doubleD
floatF
intI
longJ
shortS

而这里还有一点需要注意的是,如果是引用类型对象的数组,Class的真实类名结尾还会有一个分号;

getSimpleName()

getSimpleName()方法是jdk实现的用来快速获取当前类的真实类名的路径缩写,即不带包名的Class名称。

getCanonicalName()

getCanonicalName()方法获取到的即为java中的完整伪类名,即包名+getSimpleName()名称的完整名称,此方法获取的Class名称比较友好。

getPackage()

getPackage()则是java中默认实现的可以用来快速获取当前Class所在包名的方法,此方法仅仅返回类路径的前置(包名),不包含类名。

Class字段信息

获取完Class的名称信息以后,我们开始关注如何通过Class类获取类属性信息。我们知道在类中定义的静态和实例变量都被称为字段,在Class类中则是使用Field表示,位于java.lang.reflect包下,而在Class类中有如下方法可以获取Field信息:

 
  1. publicField[] getFields()

  2. publicField[] getDeclaredFields()

  3. publicField getField(String name)

  4. publicField getDeclaredField(String name)

可以看出来主要是分为两类,一类返回的是字段数组,一类返回具体的字段,分别看下这两类方法的作用:

getFields()/getDeclaredFields()

getFields()/getDeclaredFields()方法都是返回的当前Class中所有的字段,其中包括来自父类的,但是这两个方法在使用的时候有一定的区别,getFields()方法只能返回非私有的字段,而getDeclaredFields()则是返回所有的字段,包括私有的字段,当然还需要借助setAccessible方法才能实现。

getField(String name)/getDeclaredField(String name)

getField(String name)/getDeclaredField(String name)这两个方法都是通过字段名来获取对应的字段信息,通过命名我们不难发现,和上一组类似,getField(String name)方法只能从所有的非私有的字段中查找当前名称的字段信息,getDeclaredField(String name)则是从所有的字段中查找对应名称的字段信息。

操作Field的常见方法

通过上述的四个方法我们可以轻松获取到Class中对应的字段信息,接下来我们只要对这些信息进行操作处理,即可完成我们要做的操作,而常用的操作Field的方法如下:

 
  1. publicString getName()

  2. publicboolean isAccessible()

  3. publicvoid setAccessible(boolean flag)

  4. publicObjectget(Object obj)

  5. publicvoidset(Object obj, Object value)

getName()

getName()方法通过命名即可看出来,此方法可以获取到当前Field的字段名:

isAccessible()

isAccessible()方法我们在上述getDeclaredFields()方法的时候曾经介绍过,如果需要获取私有字段,需要setAccessible方法支持,此方法则是可以获取是否获取到setAccessible方法的支持,即是否支持获取私有字段。

setAccessible(boolean flag)

setAccessible(boolean flag)则是通过设置boolean值确认当前反射获取Field的操作中,是否检查私有字段,设置为true,则不检查,反射可以获取到私有Field,设置为false则是检查私有字段,反射不可获取私有Field。

get(Object obj)/set(Object obj, Object value)

get(Object obj)/set(Object obj, Object value)方法我们都不陌生,这一对方法则是能对当前Field设置对应的值/获取对应的值。

其他方法

除了上述常见的方法以外,开发过程中可能还会使用到一些其他操作Field的方法,搭配使用,可以实现更灵活的字段操作,方法如下:

 
  1. //返回当前字段的修饰符--public、private等

  2. publicint getModifiers()

  3. //返回当前字段的类型--String等

  4. publicClass<?> getType()

  5. //通过当前方法获取/赋值基础类型的字段值

  6. publicvoid setBoolean(Object obj, boolean z)

  7. publicboolean getBoolean(Object obj)

  8. publicvoid setDouble(Object obj, double d)

  9. publicdouble getDouble(Object obj)

  10. //获取当前字段上的注解,使用jpa或者mybatis-plus等框架的时候,会添加在字段上一些注解

  11. public<T extendsAnnotation> T getAnnotation(Class<T> annotationClass)

  12. publicAnnotation[] getDeclaredAnnotations()

Class方法信息

 

获取完Field字段信息以后,往往我们还需要进行方法的操作,比如调用xx方法实现部分功能,这个时候就需要获取方法信息,而在Class中提供了很多操作方法信息的方法,常见的如下:

 

 
  1. //获取所有的非私有方法,包括父类的非私有方法

  2. publicMethod[] getMethods()

  3. //获取所有方法,包括私有方法和父类的非私有方法

  4. publicMethod[] getDeclaredMethods()

  5. //从当前Class的所有public方法中查找对应名称,并且参数列表相同的方法(包括父类非私有方法)

  6. //如果查找不到会抛出NoSuchMethodException异常

  7. publicMethod getMethod(String name, Class<?>... parameterTypes)

  8. //从当前Class的所有方法包括父类的非私有方法中查找对应名称并且参数列表相同的方法,如果查找不到会抛出NoSuchMethodException异常

  9. publicMethod getDeclaredMethod(String name, Class<?>... parameterTypes)

而当我们通过上述方法获取到Method对象后,即可操作此对象完成Method方法调用等,而Method信息包含如下内容:

 
  1. //获取当前Method的名称

  2. publicString getName()

  3. //是否忽略检查机制,允许调用私有的Method,如果设置为true,则忽略检查,允许调用私有方法,设置false则使用检查机制,不允许操作私有方法

  4. publicvoid setAccessible(boolean flag)

  5. //调用指定obj实例对象的当前方法,并且依据参数调用正确的方法

  6. publicObject invoke(Object obj, Object... args) throws

  7. IllegalAccessException, Illegal-ArgumentException, InvocationTargetException

反射创建实例与构造方法

 

当我们拿到了字段信息和方法信息以后,这儿时候我们基本已经可以操作这些完成很多常见的功能了,但是除此之外,日常开发中还会遇到构造实例的频繁操作,如果能反射创建实例就好了,所以Class中提供了构建实例的方法,并且提供了操作Class的构造方法,如下:

 

 
  1. //获取当前Class的所有public构造方法列表

  2. publicConstructor<?>[] getConstructors()

  3. //获取当前Class中所有构造方法的列表,包含private的构造

  4. publicConstructor<?>[] getDeclaredConstructors()

  5. //根据参数列表查找当前符合的非私有构造方法,不满足的情况抛出NoSuchMethodException异常

  6. publicConstructor<T> getConstructor(Class<?>... parameterTypes)

  7. //根据参数列表查找当前所有构造中符合的方法,不满足的情况抛出NoSuchMethodException异常

  8. publicConstructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

通过调用获取的构造方法可以完成类实例的加载构建,同样的我们也可以使用如下方法快速构建类实例:

 
  1. public T newInstance() throwsInstantiationException, IllegalAccessException

举个简单的例子:

 
  1. Map<String,Integer> map = HashMap.class.newInstance();//通过Class<?>.newInstance构建类实例

  2. map.put("hello", 123);

 

 

Class的类型信息与声明信息

 

通过上述的学习,我们已经了解到Class类是个神奇的存在,既可以获取名称,也可以操作字段和方法,那么我们不禁疑惑,Class代表的类型到底是普通的类,还是内部类,还是基础类型或者数组呢?其实Class是一个类型的集合,每一个Class的类型取决于原类型,在Class方法中也提供了如下方法辅助判断Class的类型信息,如下:

 

 
  1. //Class类型是否为数组

  2. publicnativeboolean isArray()

  3. Class类型是否为基本数据类型--包装类

  4. publicnativeboolean isPrimitive()

  5. //Class类型是否为接口

  6. publicnativeboolean isInterface()

  7. //Class类型是否为枚举

  8. publicboolean isEnum()

  9. //Class类型是否为注解类型

  10. publicboolean isAnnotation()

  11. //Class类型是否为匿名内部类

  12. publicboolean isAnonymousClass()

  13. //Class类型是否为成员类,定义在方法外的Class

  14. publicboolean isMemberClass()

  15. //Class类型是否为本地类,即定义在方法内的Class,非匿名内部类

  16. publicboolean isLocalClass()

除了Class本身的类型信息,我们也可以根据以下方法获取Class的声明信息、父类、接口等信息,方法如下:

 
  1. //获取当前类的修饰符

  2. publicnativeint getModifiers()

  3. //获取当前类的父类型信息

  4. publicnativeClass<? super T> getSuperclass()

  5. //获取当前类实现的所有的接口信息

  6. publicnativeClass<?>[] getInterfaces();

  7. //获取当前类申明的注解信息数组

  8. publicAnnotation[] getDeclaredAnnotations()

  9. //获取当前类中所有的注解信息

  10. publicAnnotation[] getAnnotations()

  11. //根据注解的完整类名,查找到对应的注解信息

  12. public<A extendsAnnotation> A getAnnotation(Class<A> annotationClass)

 

 

数组与枚举的反射

 

在使用反射过程中,除了日常的操作以外,有些时候我们还需要针对数组和枚举类型的Class做一些反射操作,而数组类型的操作往往需要借助java.lang.reflect包下的Array类操作完成,主要方法如下:

 

 
  1. //创建元素类型、元素长度指定的数组

  2. publicstaticObject newInstance(Class<?> componentType, int length)

  3. //创建多维度的数组,dimensions可连续传递多个,分别代表不同维度

  4. publicstaticObject newInstance(Class<?> componentType, int... dimensions)

  5. //获取指定数组的对应索引的值

  6. publicstaticnativeObject get(Object array, int index)

  7. //赋值给指定数组的对应索引下的值

  8. publicstaticnativevoid set(Object array, int index, Object value)

  9. //获取数组长度

  10. publicstaticnativeint getLength(Object array)

需要注意的是,在Array类中,数组使用Object而不是Object[]表示,这是为了方便处理多种类型的数组而设计的,因为在java中int[]、String[]等数组都不可以与Object[]相互转化,但是却可以转为Object,例如:

 
  1. int[] intArr = (int[])Array.newInstance(int.class, 10);

  2. String[] strArr = (String[])Array.newInstance(String.class, 10);

除了数组类型,在开发中,尤其是遇到固定常量类型的时候,往往选择使用枚举类来实现操作,但是在反射中,当我们需要查找枚举类型的时候,Class类提供了如下方法获取我们枚举类中定义的所有的常量,从而可以实现枚举相关的反射操作。

  1. //获取当前枚举类型Class的所有定义的枚举常量

  2. public T[] getEnumConstants()

java 利用反射机制,获取实体所有属性和方法,并对属性赋值

一个普通的实体Person:

?

1

2

3

4

5

6

7

private int id;

private String name;

private Date createdTime;

...

//其它字段

// get set方法

...............

现在需要把通过webService传过来的实体Person里面的所有字段的null值,换成""

实现思路:

1.获取实体的所有字段,遍历
2.获取字段类型
3.调用字段的get方法,判断字段值是否为空
4.如果字段值为空,调用字段的set方法,为字段赋值

code:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

Field[] field = model.getClass().getDeclaredFields(); // 获取实体类的所有属性,返回Field数组

 try {

   for (int j = 0; j < field.length; j++) { // 遍历所有属性

     String name = field[j].getName(); // 获取属性的名字

     name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写,方便构造get,set方法

     String type = field[j].getGenericType().toString(); // 获取属性的类型

     if (type.equals("class java.lang.String")) { // 如果type是类类型,则前面包含"class ",后面跟类名

       Method m = model.getClass().getMethod("get" + name);

       String value = (String) m.invoke(model); // 调用getter方法获取属性值

       if (value == null) {

         m = model.getClass().getMethod("set"+name,String.class);

         m.invoke(model, "");

       }

     }

     if (type.equals("class java.lang.Integer")) {

       Method m = model.getClass().getMethod("get" + name);

       Integer value = (Integer) m.invoke(model);

       if (value == null) {

         m = model.getClass().getMethod("set"+name,Integer.class);

         m.invoke(model, 0);

       }

     }

     if (type.equals("class java.lang.Boolean")) {

       Method m = model.getClass().getMethod("get" + name);

       Boolean value = (Boolean) m.invoke(model);

       if (value == null) {

         m = model.getClass().getMethod("set"+name,Boolean.class);

         m.invoke(model, false);

       }

     }

     if (type.equals("class java.util.Date")) {

       Method m = model.getClass().getMethod("get" + name);

       Date value = (Date) m.invoke(model);

       if (value == null) {

         m = model.getClass().getMethod("set"+name,Date.class);

         m.invoke(model, new Date());

       }

     }

      // 如果有需要,可以仿照上面继续进行扩充,再增加对其它类型的判断

   }

 } catch (NoSuchMethodException e) {

   e.printStackTrace();

 } catch (SecurityException e) {

   e.printStackTrace();

 } catch (IllegalAccessException e) {

   e.printStackTrace();

 } catch (IllegalArgumentException e) {

   e.printStackTrace();

 } catch (InvocationTargetException e) {

   e.printStackTrace();

 }

 

Java学习笔记之使用反射+泛型构建通用DAO

先上一张原理图.

 1.首先我们如果想对User表进行操作,那么我们首先需要获取User类型.告诉BaseDaoImp,我们当前是需要对User表进行操作.因此构造函数就是用来干这个的.

 2. 当我们获取了User类型之后,如果想要对其进行操作,那么首先需要知道 sql 语句,因此我们需要对sql语句进行拼接.那么拼接过程中,我们需要知道User表内部到底声明了哪些变量.这样就需要使用反射机制.通过反射机制来获取 User实体类中声明的变量,然后对sql进行相关的拼接.那么getsql函数用来完成sql的拼接过程.

 3. 那么拼接完之后还是不行,因为拼接出来的sql语句是这样的:insert into User(id,username,password,email,grade) values(?,?,?,?,?)我们需要对占位符进行赋值操作.那么首先我们需要获取具体的值,那么setArgs就是来获取属性的具体值的.

 4.当获取了具体的值之后,我们就可以通过sql提供给我们的相关函数来执行sql语句了.

 这里函数其实都非常的简单,只要细看,还是能明白其中的道理的.

package com.example.daoimp;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.example.dao.BaseDao;
import com.example.helper.JdbcDaoHelper;

//通用DAO

public class BaseDaoImp<T> implements BaseDao<T> {

    /** 操作常量 */
    public static final String SQL_INSERT = "insert";
    public static final String SQL_UPDATE = "update";
    public static final String SQL_DELETE = "delete";
    public static final String SQL_SELECT = "select";

    private Class<T> EntityClass; // 获取实体类

    private PreparedStatement statement;

    private String sql;          

    private Object argType[];

    private ResultSet rs;

    @SuppressWarnings("unchecked")
    public BaseDaoImp() {
        
        /**
         *  传递User就是 com.example.daoimp.BaseDaoImp<com.example.bean.User>
         *  传递Shop就是 com.example.daoimp.BaseDaoImp<com.example.bean.Shop>
         * */
        ParameterizedType type = (ParameterizedType) getClass()
                .getGenericSuperclass();      
        
        /**
         * 这里如果传递的是User.那么就是class com.example.bean.User 
         * 如果传递的是Shop.       那么就是class com.example.bean.Shop
         * */
        EntityClass = (Class<T>) type.getActualTypeArguments()[0];  
    }

    @Override
    public void add(T t) {
        // TODO Auto-generated method stub
        sql = this.getSql(SQL_INSERT);   //获取sql.
        // 赋值.
        try {
            argType = setArgs(t, SQL_INSERT);
            statement = JdbcDaoHelper.getPreparedStatement(sql);  //实例化PreparedStatement.
            //为sql语句赋值.
            statement = JdbcDaoHelper.setPreparedStatementParam(statement,
                    argType);
            statement.executeUpdate(); //执行语句.
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            JdbcDaoHelper.release(statement, null);  //释放资源.
        }
    }

    @Override
    public void delete(T t) {
        // TODO Auto-generated method stub
        sql = this.getSql(SQL_DELETE);
        try {
            argType = this.setArgs(t, SQL_DELETE);
            statement = JdbcDaoHelper.getPreparedStatement(sql);
            statement = JdbcDaoHelper.setPreparedStatementParam(statement,
                    argType);
            statement.executeUpdate();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            JdbcDaoHelper.release(statement, null);
        }
    }

    @Override
    public void update(T t) {
        // TODO Auto-generated method stub
        sql = this.getSql(SQL_UPDATE);
        try {
            argType = setArgs(t, SQL_UPDATE);
            statement = JdbcDaoHelper.getPreparedStatement(sql);
            statement = JdbcDaoHelper.setPreparedStatementParam(statement,
                    argType);
            statement.executeUpdate();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            JdbcDaoHelper.release(statement, null);
        }
    }

    @Override
    public T select(T t) {
        // TODO Auto-generated method stub
        sql = this.getSql(SQL_SELECT);
        T obj = null;
        try {
            argType = setArgs(t, SQL_SELECT);
            statement = JdbcDaoHelper.getPreparedStatement(sql);
            statement = JdbcDaoHelper.setPreparedStatementParam(statement,
                    argType);
            rs = statement.executeQuery();
            Field fields[] = EntityClass.getDeclaredFields();
            while (rs.next()) {
                obj = EntityClass.newInstance();
                for (int i = 0; i < fields.length; i++) {
                    fields[i].setAccessible(true);
                    fields[i].set(obj, rs.getObject(fields[i].getName()));
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return obj;

    }

    // sql拼接函数 形如 : insert into User(id,username,password,email,grade) values(?,?,?,?,?)
    private String getSql(String operator) {

        StringBuffer sql = new StringBuffer();
        // 通过反射获取实体类中的所有变量
        Field fields[] = EntityClass.getDeclaredFields();

        // 插入操作
        if (operator.equals(SQL_INSERT)) {
            sql.append("insert into " + EntityClass.getSimpleName());
            sql.append("(");
            for (int i = 0; fields != null && i < fields.length; i++) {
                fields[i].setAccessible(true);    //这句话必须要有,否则会抛出异常.
                String column = fields[i].getName();
                sql.append(column).append(",");
            }
            sql = sql.deleteCharAt(sql.length() - 1);
            sql.append(") values (");
            for (int i = 0; fields != null && i < fields.length; i++) {
                sql.append("?,");
            }
            sql.deleteCharAt(sql.length() - 1);
            // 是否需要添加分号
            sql.append(")");
        } else if (operator.equals(SQL_UPDATE)) {
            sql.append("update " + EntityClass.getSimpleName() + " set ");
            for (int i = 0; fields != null && i < fields.length; i++) {
                fields[i].setAccessible(true);
                String column = fields[i].getName();
                if (column.equals("id")) {
                    continue;
                }
                sql.append(column).append("=").append("?,");
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(" where id=?");
        } else if (operator.equals(SQL_DELETE)) {
            sql.append("delete from " + EntityClass.getSimpleName()
                    + " where id=?");
        } else if (operator.equals(SQL_SELECT)) {
            sql.append("select * from " + EntityClass.getSimpleName()
                    + " where id=?");
        }
        return sql.toString();
    }

    // 获取参数.
    private Object[] setArgs(T entity, String operator)
            throws IllegalArgumentException, IllegalAccessException {

        Field fields[] = EntityClass.getDeclaredFields();
        if (operator.equals(SQL_INSERT)) {

            Object obj[] = new Object[fields.length];
            for (int i = 0; obj != null && i < fields.length; i++) {
                fields[i].setAccessible(true);
                obj[i] = fields[i].get(entity);
            }
            return obj;

        } else if (operator.equals(SQL_UPDATE)) {

            Object Tempobj[] = new Object[fields.length];
            for (int i = 0; Tempobj != null && i < fields.length; i++) {
                fields[i].setAccessible(true);
                Tempobj[i] = fields[i].get(entity);
            }

            Object obj[] = new Object[fields.length];
            System.arraycopy(Tempobj, 1, obj, 0, Tempobj.length - 1);
            obj[obj.length - 1] = Tempobj[0];
            return obj;

        } else if (operator.equals(SQL_DELETE)) {

            Object obj[] = new Object[1];
            fields[0].setAccessible(true);
            obj[0] = fields[0].get(entity);
            return obj;
        } else if (operator.equals(SQL_SELECT)) {

            Object obj[] = new Object[1];
            fields[0].setAccessible(true);
            obj[0] = fields[0].get(entity);
            return obj;
        }
        return null;
    }

}

这样就对BaseDao进行了具体的实现.因为我们的User表还有其他额外的操作,那么我们只需要这样.它通过继承BaseDaoImp,然后实现UserDao接口,那么UserDaoImp就即具有了BaseDaoImp的通用方法,还具有了自己其他的额外方法.

复制代码

package com.example.daoimp;

import java.lang.reflect.ParameterizedType;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import com.example.bean.User;
import com.example.dao.UserDao;
import com.example.helper.JdbcDaoHelper;

public class UserDaoImp extends BaseDaoImp<User> implements UserDao {

    private Class<?> EntityClass;

    private String sql;

    private PreparedStatement statement;

    private ResultSet rs;

    private List<User> list;

    public UserDaoImp() {

        ParameterizedType type = (ParameterizedType) getClass()
                .getGenericSuperclass();
        EntityClass = (Class<?>) type.getActualTypeArguments()[0];
    }

    @Override
    public List<User> findAll() {
        // TODO Auto-generated method stub
        StringBuffer b = new StringBuffer();
        list = new ArrayList<User>();
        sql = b.append("select * from " + EntityClass.getSimpleName())
                .toString();
        try {
            statement = JdbcDaoHelper.getPreparedStatement(sql);
            rs = statement.executeQuery();
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setPassword(rs.getString("password"));
                user.setEmail(rs.getString("email"));
                user.setUsername(rs.getString("username"));
                user.setGrade(rs.getInt("grade"));
                list.add(user);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return list;
    }

}

复制代码

 有 了他们,我们就可以进行具体的操作了,如果还有Shop表,那么同理我们可以去创建一个ShopDao去继承BaseDao,然后在自己的ShopDao 定义其他的额外方法就可以了.当表非常多的时候,我们就可以采用这种思想进行封装.这样写出的代码质量就显得非常的高,耦合度也非常的松散.

 在添加上工具类.

复制代码

package com.example.helper;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcDaoHelper {

    private static final String USER = "root";

    private static final String PASSWORD = "";

    private static final String URL = "jdbc:mysql://localhost:3306/usermanager";

    private static Connection con;

    // 获取数据库连接对象
    public static Connection getConnection() {

        if (con == null) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                con = DriverManager.getConnection(URL, USER, PASSWORD);
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            return con;
        }
        return con;
    }

    public static PreparedStatement getPreparedStatement(String sql)
            throws SQLException {

        return getConnection().prepareStatement(sql);
    }

    public static PreparedStatement setPreparedStatementParam(
            PreparedStatement statement, Object obj[]) throws SQLException {

        for (int i = 0; i < obj.length; i++) {
            statement.setObject(i + 1, obj[i]);
        }
        return statement;
    }

    // 释放资源
    public static void release(PreparedStatement ps, ResultSet rs) {
        try {
            if (con != null) {
                con.close();
                con = null;
            }
            if (ps != null) {
                ps.close();
                ps = null;
            }
            if (rs != null) {
                rs.close();
                rs = null;
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

}

复制代码

 最后加上UserBean.

复制代码

package com.example.bean;

public class User {

    private int id;
    private String username;
    private String password;
    private String email;
    private int grade;
    
    public User(){
        
    }
    
    public User(int id,String username,String password,String email,int grade){
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
        this.grade = grade;
    }

    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 getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }
    
}

复制代码

 测试类.

复制代码

package com.example.jdbc;

import java.util.List;

import com.example.bean.User;
import com.example.daoimp.UserDaoImp;

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
    
        List<User>list = null;
        UserDaoImp imp = new UserDaoImp();
        list = imp.findAll();
        for(User user:list){
            System.out.println(user.getId()+" "+user.getUsername()+" "+user.getPassword()+" "+user.getEmail()+" "+user.getGrade());
        }
        //insert操作.
        User user = new User();
        user.setId(1);
        user.setUsername("代码如风");
        user.setPassword("123456");
        user.setEmail("123");
        user.setGrade(5);
        imp.add(user);
        //update操作.
        User user_1 = new User();
        user.setId(1);
        user.setUsername("心静如水");
        user.setPassword("123456");
        user.setEmail("123");
        user.setGrade(5);
        imp.update(user_1);
    }

}

java反射之通过反射了解集合泛型的本质(详解)

1、初始化两个集合,一个使用泛型,一个不使用

?

1

2

ArrayList list1 = new ArrayList();

ArrayList<String> list2 = new ArrayList<String>();

2、有定义类型可得在list2中添加int类型会报错

?

1

2

list2.add("Hello");

list2.add(20); //报错

3、获取两个对象的类类型进行比较

?

1

2

3

Class c1 = list1.getClass();

Class c2 = list2.getClass();

System.out.println(c1 == c2);

通过c1==c2结果返回true,说明编译之后集合的泛型是去泛型化的,java中集合的泛型是为了防止错误输入的,只在编译阶段有效,绕过编译就无效了

4、验证:通过方法的反射来绕过编译

?

1

2

3

4

5

6

7

try {

 Method m = c2.getMethod("add", Object.class);

 m.invoke(list2,20);

 System.out.println(list2);

} catch (Exception e) {

 e.printStackTrace();

}

5、输出结果

 

6、完整代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

package com.format.test;

 

import java.lang.reflect.Method;

import java.util.ArrayList;

 

/**

 * Created by Format on 2017/6/4.

 */

public class Test2 {

 public static void main(String[] args) {

  ArrayList list1 = new ArrayList();

  ArrayList<String> list2 = new ArrayList<String>();

  list2.add("Hello");

//  list2.add(20); //报错

  Class c1 = list1.getClass();

  Class c2 = list2.getClass();

  System.out.println(c1 == c2);

  /**

   * 反射操作都是编译之后的操作

   * c1==c2结果返回true,说明编译之后集合的泛型是去泛型化的

   * java中集合的泛型是为了防止错误输入的,只在编译阶段有效,绕过编译就无效了

   * 验证:通过方法的反射来绕过编译

   */

  try {

   Method m = c2.getMethod("add", Object.class);

   m.invoke(list2,20);

   System.out.println(list2);

  } catch (Exception e) {

   e.printStackTrace();

  }

 }

}

https://www.jianshu.com/p/607ff4e79a13

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值