Java泛型总结
来源:互联网 发布:达内java视频百度网盘 编辑:程序博客网 时间:2024/06/07 17:43
泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。
泛型机制将类型转换时的类型检查从运行时提前到了编译时,使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。
泛型程序设计意味着程序可以被不同类型的对象重用,类似c++的模版。
泛型对于集合类尤其有用,如ArrayList。这里可能有疑问,既然泛型为了适应不同的对象,ArrayList本来就可以操作不同类型的对象呀?那是因为没有泛型之前采用继承机制实现的,实际上它只维护了一个Object对象的数组。结果就是对List来说它只操作了一类对象Object,而在用户看来却可以保存不同的对象。
泛型提供了更好的解决办法——类型参数,如:
List<String> list =newArrayList<String>();
这样解决了几个问题:
1 可读性,从字面上就可以判断集合中的内容类型;
2 类型检查,避免插入非法类型。
3 获取数据时不在需要强制类型转换。
泛型类
public class Pair<T>{
private Tfield1;
}
其中 <T> 是类型参数定义。
使用时:Pair<String> p= newPair<String>();
此时类内部的field1就是字符串类型了。
如果引用多个类型,可以使用逗号分隔:<S,D>
类型参数名可以使用任意字符串,建议使用有代表意义的单个字符,以便于和普通类型名区分,如:T代表type,有原数据和目的数据就用S,D,子元素类型用E等。当然,你也可以定义为XYZ,甚至xyZ。
泛型方法
泛型方法定义如下:
public static <T> Tmarshalle(Targ){}
与泛型类一样,<T> 是类型参数定义。如:
public class GenericMethod {
publicstatic <T> T getMiddle(T... a){
returna[a.length/2];
}
}
严格的调用方式:
Stringo=GenericMethod.<String>getMiddle("213","result","12");
一般情况下调用时可以省略,看起来就像定义String类型参数的方法:
GenericMethod.getMiddle(String,String,String),这是因为jdk会根据参数类型进行推断。看一下下面的例子:
Objecto=GenericMethod.getMiddle("213",0,"12");
System.out.println(o.getClass());
System.out.println(o);
输出结果为:
class java.lang.Integer
0
这是因为jdk推断三个参数的共同父类,匹配为Object,那么相当于:
Objecto=GenericMethod.<Object>getMiddle("213",0,"12");
习惯了类型参数放在类的后面,如ArrayList<String>,泛型方法为什么不放在后面?看一个例子:
public static <T,S> Tf(T t){returnt;}
public static class a{}
public static class b{}
//尽量恶心一点
@Test
public void test(){
ac=new a();
<a,b>f(c);//OK
f<a,b>(c);//error,看起来像是一个逗号运算符连接的两个逻辑表达式,当然目前java中除了for(...)并不支持逗号运算符
}
因此,为了避免歧义,jdk采用类型限定符前置。
泛型方法与泛型类的方法
如果泛型方法定义在泛型类中,而且类型参数一样:
public classGenericMethod<T> {
public<T> void sayHi(T t){
System.out.println("Hi"+t);
}
}
是不是说,定义GenericMethod时传了 Integer 类型,sayHi()也就自动变成 Integer 了呢?No。
String i="abc";
newGenericMethod<Integer>().<String>sayHi(i);
该代码运行一点问题都没有。原因就在于泛型方法中的<T>,如果去掉它,就有问题了。
The method sayHi(Integer) inthe typeGenericMethod<Integer> is not applicable for the arguments
(String)
小结:
泛型方法有自己的类型参数,泛型类的成员方法使用的是当前类的类型参数。
方法中有<T> 是泛型方法;没有的,称为泛型类中的成员方法。
类型参数的限定
如果限制只有特定某些类可以传入T参数,那么可以对T进行限定,如:只有实现了特定接口的类:<TextendsComparable>,表示的是Comparable及其子类型。
为什么是extends不是 implements,或者其他限定符?
严格来讲,该表达式意味着:`TsubtypeOf Comparable`,jdk不希望再引入一个新的关键词;
其次,T既可以是类对象也可以是接口,如果是类对象应该是`implements`,而如果是接口,则应该是`extends`;从子类型上来讲,extends更接近要表达的意思。
好吧,这是一个约定。
限定符可以指定多个类型参数,分隔符是 &,不是逗号,因为在类型参数定义中,逗号已经作为多个类型参数的分隔符了,如:<T,S extends Comparable & Serializable>。
泛型限定的优点:
限制某些类型的子类型可以传入,在一定程度上保证类型安全;
可以使用限定类型的方法。如:
public classParent<T>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
//这里只能使用name自object继承的方法
this.name= name;
}
}
加上限定符,就可以访问限定类型的方法了,类型更明确。
public class Parent<TextendsList<T>>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
//这里可以访问List的方法,如name.size()
this.name= name;
}
}
注:
我们知道final类不可继承,在继承机制上class SomeString extendsString是错误的,但泛型限定符使用时是可以的:<T extends String>,只是会给一个警告。
后面的通配符限定有一个super关键字,这里没有。
泛型擦除
泛型只在编译阶段有效,编译后类型被擦除了,也就是说jvm中没有泛型对象,只有普通对象。所以完全可以把代码编译为jdk1.0可以运行的字节码。
擦除的方式
定义部分,即尖括号中间的部分直接擦除。
public classGenericClass<T extendsComparable>{}
擦除后:
public class GenericClass{}
引用部分如:
public T field1;
其中的T被替换成对应的限定类型,擦除后:
public Comparable field1;
如果没有限定类型:
public classGenericClass<T>{
public T field1;
}
那么的替换为object,即:
public class GenericClass{
public Object field1;
}
有多个限定符的,替换为第一个限定类型名。如果引用了第二个限定符的类对象,编译器会在必要的时候进行强制类型转换。
public classGenericClass<T extendsComparable & Serializable>{
public T field1;
}
类擦除后变为:
public class GenericClass{
public Comparablefield1;
}
而表达式返回值返回时,泛型的编译器自动插入强制类型转换。
泛型擦除的残留
反编译GenericClass:
Compiled from"GenericClass.java"
publicclasscom.pollyduan.generic.GenericClass<T> {
public T field1;
publiccom.pollyduan.generic.GenericClass();
}
好像前面说的不对啊,这还是T啊,没有擦除呀?
这就是擦除的残留。反汇编:
{
public T field1;
descriptor:Ljava/lang/Object;
flags: ACC_PUBLIC
Signature: #8 // TT;
publiccom.pollyduan.generic.GenericClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1,locals=1, args_size=1
0:aload_0
1:invokespecial#12 //Method java/lang/Object."<init>":()V
4:return
LineNumberTable:
line2: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/pollyduan/generic/GenericClass;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this Lcom/pollyduan/generic/GenericClass<TT;>;
}
SourceFile:"GenericClass.java"
Signature: #22//<T:Ljava/lang/Object;>Ljava/lang/Object;
其中:
descriptor:对方法参数和返回值进行描述;
signature:泛型类中独有的标记,普通类中没有,JDK5才加入,标记了定义时的成员签名,包括定义时的泛型参数列表,参数类型,返回值等;
可以看到public T field1;是签名,还保留了定义的格式;其对应的参数类型是Ljava/lang/Object;。
最后一行是类的签名,可以看到T后面有跟了擦除后的参数类型:<T:Ljava/lang/Object;>。
这样的机制,对于分析字节码是有意义的。
泛型的约束和限制
不能使用8个基本类型实例化类型参数
原因在于类型擦除,Object不能存储基本类型:
byte,char,short,int,long,float,double,boolean
从包装类角度来看,或者说三个:
Number(byte,short,int,long,float,double),char,boolean
类型检查不可使用泛型
if(aaainstanceofPair<String>){}//error
Pair<String> p =(Pair<String>)a;//warn
Pair<String> p;
Pair<Integer> i;
i.getClass()==p.getClass();//true
不能创建泛型对象数组
GenericMethod<User>[]o=null;//ok
o=newGenericMethod<User>[10];//error
可以定义泛型类对象的数组变量,不能创建及初始化。
注,可以创建通配类型数组,然后进行强制类型转换。不过这是类型不安全的。
o=(GenericMethod<User>[])newGenericMethod<?>[10];
不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此jdk强制不允许。
有一个特例是方法的可变参数,虽然本质上是数组,却可以使用泛型。
安全的方法是使用List。
Varargs警告
java不支持泛型类型的对象数组,可变参数是可以的。它也正是利用了强制类型转换,因此同样是类型不安全的。所以这种代码编译器会给一个警告。
public static <T> TgetMiddle(T...a){
return a[a.length/2];
}
去除警告有两种途径:一种是在定义可变参数方法上(本例中的getMiddle())加上@SafeVarargs注解,另一种是在调用该方法时添加@SuppressWarnings("unchecked")注解。
不能实例化泛型对象
T t= new T();//error
T.class.newInstance();//error
T.class;//error
解决办法是传入Class<T> t参数,调用t.newInstance()。
public void sayHi(Class<T>c){
Tt=null;
try{
t=c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("Hi"+t);
}
不能在泛型类的静态域中使用泛型类型
public classSingleton<T>{
privatestatic T singleton; //error
publicstatic T getInstance(){} //error
publicstatic void print(T t){} //error
}
但是,静态的泛型方法可以使用泛型类型:
public static <T> TgetInstance(){returnnull;} //ok
public static <T> voidprint(T t){}//ok
这个原因很多资料中都没说的太明白,说一下个人理解,仅供参考:
1. 泛型类中,<T>称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:`private T t;`,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的。
2. 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T所指的具体类型已经明确了。
不能捕获泛型类型的对象
Throwable类不可以被继承,自然也不可能被catch。
public classGenericThrowable<T>extends Throwable{
//The generic classGenericThrowable<T> may not subclassjava.lang.Throwable
}
但由于Throwable可以用在泛型类型参数中,因此可以变相的捕获泛型的Throwable对象。
@Test
public voidtestGenericThrowable(){
GenericThrowable<RuntimeException>obj=newGenericThrowable<RuntimeException>();
obj.doWork(newRuntimeException("why?"));
}
public static classGenericThrowable<Textends Throwable>{
public void doWork(Tt) throws T{
try{
inti=3/0;
}catch(Throwablecause){
t.initCause(cause);
throwt;
}
}
}
这个能干什么?
@Test
public voidtestGenericThrowable(){
GenericThrowable<RuntimeException>obj=newGenericThrowable<RuntimeException>();
obj.doWork(newRuntimeException("What did you do?"));
}
public static classGenericThrowable<Textends Throwable>{
public void doWork(Tt) throws T{
try{
Readerreader=new FileReader("notfound.txt");
//这里应该是checked异常
}catch(Throwablecause){
t.initCause(cause);
throwt;
}
}
}
FileReader实例化可能抛出已检查异常,jdk中要求必须捕获或者抛出已检查异常。这种模式把它给隐藏了。也就是说可以消除已检查异常,有点不地道,颠覆了java异常处理的认知,后果不可预料,慎用。
擦除的冲突
重载与重写
定义一个普通的父类:
packagecom.pollyduan.generic;
public class Parent{
publicvoid setName(Object name) {
System.out.println("Parent:"+ name);
}
}
那么继承一个子类,Son.java
packagecom.pollyduan.generic;
public class Son extendsParent {
publicvoid setName(String name) {
System.out.println("son:"+ name);
}
publicstatic void main(String[] args) {
Sonson=new Son();
son.setName("abc");
son.setName(newObject());
}
}
Son类重载了一个setName(String)方法,这没问题。输出:
son:abc
Parent:java.lang.Object@6d06d69c
Parent修改泛型类:
packagecom.pollyduan.generic;
public classParent<T>{
publicvoid setName(T name) {
System.out.println("Parent:"+ name);
}
}
从擦除的机制得知,擦除后的class文件为:
packagecom.pollyduan.generic;
public class Parent{
publicvoid setName(Object name) {
System.out.println("Parent:"+ name);
}
}
这和最初的非泛型类是一样的,那么Son类修改为:
packagecom.pollyduan.generic;
public class SonextendsParent<String> {
publicvoid setName(String name) {
System.out.println("son:"+ name);
}
publicstatic void main(String[] args) {
Son son=new Son();
son.setName("abc");
son.setName(newObject());//The method setName(String) in the type Sonis not applicable for thearguments (Object)
}
}
发现重载无效了。这是泛型擦除造成的,无论是否在setName(String)是否标注为@Override都将是重写,都不是重载。而且,即便你不写setName(String)方法,编译器已经默认重写了这个方法。
换一个角度来考虑,定义Son时,Parent已经明确了类型参数为String,那么再写setName(Stirng)是重写,也是合理的。
packagecom.pollyduan.generic;
public class SonextendsParent<String> {
publicstatic void main(String[] args) {
Sonson=new Son();
son.setName("abc");//ok
}
}
反编译会发现,编译器在内部编译了两个方法:
public voidsetName(java.lang.String);
public voidsetName(java.lang.Object);
setName(java.lang.Object) 虽然是public但编码时会发现不可见,它称为"桥方法",它会重写父类的方法。
Son son=new Son();
Parent p=son;
p.setName(new Object());
强行调用会转换异常,也就证明了它实际上调用的是son的setName(String)。
我非要重载怎么办?只能曲线救国,改个名字吧。
public void setName2(Stringname) {
System.out.println("son:"+ name);
}
继承泛型的参数化
一个泛型类的类型参数不同,称之为泛型的不同参数化。
泛型有一个原则:一个类或类型变量不可成为两个不同参数化的接口类型的子类型。如:
packagecom.pollyduan.generic;
import java.util.Comparator;
public class Parentimplements Comparator{
@Override
public intcompare(Object o1, Object o2) {
return0;
}
}
public class Son extendsParent implements Comparator {
}
这样是没有问题的。如果增加了泛型参数化:
package com.pollyduan.generic;
import java.util.Comparator;
public class ParentimplementsComparator<Parent>{
@Override
public intcompare(Parent o1, Parent o2) {
return0;
}
}
packagecom.pollyduan.generic;
import java.util.ArrayList;
import java.util.Comparator;
public class Son extendsParent implements Comparator<Son> {
//The interfaceComparator cannot be implemented more than once withdifferent arguments
}
原因是Son实现了两次Comparator<T>,擦除后均为Comparator<Object>,造成了冲突。
通配符类型
通配符是在泛型类使用时的一种机制,不能用在泛型定义时的泛型表达式中(这是泛型类型参数限定符)。
子类型通配符
如果P是S的超类,那么 Pair<S>就是Pair<? extends P>的子类型,通配符就是为了解决这个问题的。
这称为子类型限定通配符,又称上边界通配符(upperbound wildcardGenerics),代表继承它的所有子类型,通配符匹配的类型不允许作为参数传入,只能作为返回值。
public static void test1() {
Parent<Integer> bean1= new Parent<Integer>();
bean1.setName(123);
Parent<? extendsNumber> bean2 = bean1;
Integer i = 100;
bean2.setName(i);// 编译错误
Number s =bean2.getName();
System.out.println(s);
}
getName()的合理性:
无论bean2指向的是任何类型的对象,只要是Number的子类型,都可以用Number类型变量接收。
为什么setName(str)会抛出异常呢?
1. <? extends Number> 表明了入参是Number的子类型;
2. 那么bean2 可以指向Parent<Integer>,也可以指向Parent<Double>,这都是符合规则的;
3. 再看setName(<? extendsNumber>),逻辑上传入Integer或者Double对象都是符合逻辑的;
4. 如果bean2指向的是Parent<Integer>,而传入的对象是Double的,两个看似合理的规则到一起就不行了。
5. 因此,jdk无法保证类型的安全性,干脆不允许这样——不允许泛型的子类型通配类型作为入参。
超类型通配符
与之对应的是超类型 Pair<?super P>,又称下边界通配符(lowerboundwildcard Generics),通配符匹配的类型可以为方法提供参数,不能得到返回值。
public static void test2(){ public static void test2() {
Parent<Number>bean1 = new Parent<Number>();
bean1.setName(123);
Parent<?super Integer> bean2 = bean1;
Integeri = 100;
bean2.setName(i);
Integers = bean2.getName();// 编译错误
Objecto = bean2.getName();// ok
System.out.println(o);
}
}
setName的可行性:
1. 无论bean2指向Parent<Number>,Parent<Integer>还是Parent<Object>都是允许的;
2. 都可以传入Integer或Integer的子类型。
getName为毛报错?
1. 由于限定类型的超类可能有很多,getName返回类型不可预知,如Integer或其父类型Number/OtherParentClass...都无法保证类型检查的安全。
2. 但是由于Java的所有对象的顶级祖先类都是Object,因此可以用Object获取getName返回值。
无限定通配符
Pair<?> 就是 Pair<? extendsObject>
因此,无限定通配符可以作为返回值,不可做入参。
返回值只能保存在Object中。
P<?> 和P
Pair可以调用setter方法,这是它和Pair<?>最重要的区别。
P<?> 不等于 P<Object>
P<Object>是P<?>的子类。
类型通配符小结
1. 限定通配符总是包括自己;
2. 子类型通配符:set方法受限,只可读,不可写;
3. 超类型通配符:get方法受限,不可读(Object除外),只可写;
4. 无限定通配符,只可读不可写;
5. 如果你既想存,又想取,那就别用通配符;
6. 不可同时声明子类型和超类型限定符,及extends和super只能出现一个。
通配符的受限只针对setter(T)和T getter(),如果定义了一个setter(Integer)这种具体类型参数的方法,无限制。
通配符捕获
通配符限定类中可以使用T,编译器适配类型。
有一个键值对的泛型类:
@Data
class Pair<T> {
privateTkey;
private Tvalue;
}
使用通配类型创建一个swap方法交换key-value,交换时需要先使用一个临时变量保存一个字段:
public static voidswap(Pair<?> p){
// ?k=p.getKey();//error,?不可作为具体类型限定符
Object k=p.getKey();//好吧,换成object,ok
p.setKey(p.getValue());//but,通配符类型不可做入参
p.setValue(k);
}
这里有一个办法解决它,再封装一个swapHelper():
private static <T>voidswapHelper(Pair<T> p){
Tk=p.getKey();
p.setKey(p.getValue());
p.setValue(k);
}
public static voidswap(Pair<?> p){
swapHelper(p);
}
这种方式,称为:通配符捕获,用一个Pair<T>来捕获 Pair<?>中的类型。
注:
当然,你完全可以直接使用swapHelper,这里只是为了说明这样一种捕获机制。
只允许捕获单个、确定的类型,如:ArrayList<Pair<?>>是无法使用ArrayList<Pair<T>> 捕获的。
泛型与继承
继承的原则
继承泛型类时,必须对父类中的类型参数进行初始化。或者说父类中的泛型参数必须在子类中可以确定具体类型。
例如:有一个泛型类Parent<T>,那么Son类定义时有两种方式初始化父类型的类型参数:
1 用具体类型初始化:
public class SonextendsParent<String>{}
2 用子类中的泛型类型初始化父类:
public class Son<T>extendsParent<T>{}
Pair<P>和Pair<S>
无论P和S有什么继承关系,一般Pair<P>和Pair<S>没什么关系。
Pair<Son> s=newPair<>();
Pair<Parent>p=s;//error
Parent<T>和Son<T>
泛型类自身可以继承其他类或实现接口,如List<T>实现ArrayList<T>
泛型类可以扩展泛型类或接口,如ArrayList<T>实现了 List<T>,此时ArrayList<T>可以转换为List<T>。这是安全的。
Parent<T>和Parent
Parent<T>随时都可以转换为原生类型Parent,但需要注意类型检查的安全性。
packagecom.pollyduan.generic;
import java.io.File;
class Parent<T>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
this.name= name;
}
publicstatic void main(String[] args) {
Parent<String>p1=new Parent<>();
p1.setName("tom");
System.out.println(p1.getName());
Parent p2=p1;
p2.setName(newFile("1.txt"));//严重error
System.out.println(p2.getName());
}
}
运行没有异常,注意。
Person<? extends XXX>
严格讲通配符限定的泛型对象不属于继承范畴,但使用中有类似继承的行为。
Son是Parent的子类型,那么Person<?extendsSon>就是Person<?extendsParent> 的子类型。
Person<? extendsObject> 等同于 Person<?>,那么基于上以规则可以推断:Person<?extendsParent> 是Person<?> 的子类型。
Person<Object> 是 Person<?> 的子类型。
泛型与反射
泛型相关的反射
有了泛型机制,jdk的reflect包中增加了几个泛型有关的类:
Class<T>.getGenericSuperclass()
获取泛型超类
ParameterizedType
类型参数实体类
实例
基于泛型的通用JDBC DAO。
User.java
packagecom.pollyduan.generic;
@Data
public class User {
privateInteger id;
privateString name;
}
AbstractBaseDaoImpl.java
packagecom.pollyduan.generic;
public abstractclassAbstractBaseDaoImpl<T> {
publicAbstractBaseDaoImpl() {
Typet = getClass().getGenericSuperclass();
System.out.println(t);
}
}
UserDaoImpl.java
packagecom.pollyduan.generic;
public class UserDaoImplextendsAbstractBaseDaoImpl<User> {
publicstatic void main(String[] args) {
UserDaoImpluserDao=new UserDaoImpl();
}
}
运行UserDaoImpl.main(),输出:
com.pollyduan.generic.AbstractBaseDaoImpl<com.pollyduan.generic.User>
可以看到,在抽象类AbstractBaseDaoImpl中可以拿到泛型类的具体类。
从这一机制,可以通过AbstractBaseDaoImpl实现通用的JDBA DAO。
完善AbstractBaseDaoImpl.java
packagecom.pollyduan.generic;
importjava.lang.reflect.Field;
importjava.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
importjava.util.stream.Collectors;
public abstractclassAbstractBaseDaoImpl<T, K> {
privateClass<T> entityClass;
privateClass<T> primaryKeyClass;
publicAbstractBaseDaoImpl() {
Typet = getClass().getGenericSuperclass();
ParameterizedTypept = (ParameterizedType) t;
Type[]typeParameters = pt.getActualTypeArguments();
entityClass= (Class<T>) typeParameters[0];
primaryKeyClass= (Class<T>) typeParameters[1];
}
publicvoid save(T t) {
StringBuildersb = new StringBuilder("INSERT INTO ");
sb.append(entityClass.getSimpleName());
sb.append("(");
Field[]fields = entityClass.getDeclaredFields();
StringfieldNames = Arrays.asList(fields).stream().map(x->x.getName()).collect(Collectors.joining(","));
sb.append(fieldNames);
sb.append(")VALUES(");
sb.append(fieldNames.replaceAll("[^,]+","?"));
sb.append(")");
System.out.println(sb.toString());
//根据反射还要遍历fields处理变量绑定,略。
}
publicvoid delete(K k) {
StringBuildersb = new StringBuilder("DELETE FROM ");
sb.append(entityClass.getSimpleName());
sb.append("WHERE ID=?");// 这里默认主键名为id,应该配合注解动态获取主键名
System.out.println(sb.toString());
}
publicvoid update(T t) {
StringBuildersb = new StringBuilder("UPDATE ");
sb.append(entityClass.getSimpleName());
sb.append("SET ");
Field[]fields = entityClass.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
if(fields[i].getName().toLowerCase().equals("id")) {
continue;
}
sb.append(fields[i].getName());
sb.append("=?");
if(i < fields.length - 1) {
sb.append(",");
}
}
sb.append("WHERE ID=?");
System.out.println(sb.toString());
}
public Tget() throws Exception {
Tt = null;
//模拟resultset
Map<String,Object> rs = new HashMap<>();
t= entityClass.newInstance();
Field[]fields = entityClass.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
field.set(t,rs.get(field.getName()));
}
returnt;
}
public static voidmain(String[] args) {
UserDaoImpluserDao=new UserDaoImpl();
Useruser1=new User();
userDao.save(user1);
userDao.delete(1);
userDao.update(user1);
try {
Useruser2=userDao.get();
System.out.println(user2);
}catch(Exception e) {
e.printStackTrace();
}
}
}
有现成的ORM框架可用,这里就意思意思得了。输出:
INSERT INTO User(id,name)VALUES(?,?)
DELETE FROM User WHERE ID=?
UPDATE User SET name=? WHEREID=?
User(id=1, name=Peter)
泛型机制将类型转换时的类型检查从运行时提前到了编译时,使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。
泛型程序设计意味着程序可以被不同类型的对象重用,类似c++的模版。
泛型对于集合类尤其有用,如ArrayList。这里可能有疑问,既然泛型为了适应不同的对象,ArrayList本来就可以操作不同类型的对象呀?那是因为没有泛型之前采用继承机制实现的,实际上它只维护了一个Object对象的数组。结果就是对List来说它只操作了一类对象Object,而在用户看来却可以保存不同的对象。
泛型提供了更好的解决办法——类型参数,如:
List<String> list =newArrayList<String>();
这样解决了几个问题:
1 可读性,从字面上就可以判断集合中的内容类型;
2 类型检查,避免插入非法类型。
3 获取数据时不在需要强制类型转换。
泛型类
public class Pair<T>{
private Tfield1;
}
其中 <T> 是类型参数定义。
使用时:Pair<String> p= newPair<String>();
此时类内部的field1就是字符串类型了。
如果引用多个类型,可以使用逗号分隔:<S,D>
类型参数名可以使用任意字符串,建议使用有代表意义的单个字符,以便于和普通类型名区分,如:T代表type,有原数据和目的数据就用S,D,子元素类型用E等。当然,你也可以定义为XYZ,甚至xyZ。
泛型方法
泛型方法定义如下:
public static <T> Tmarshalle(Targ){}
与泛型类一样,<T> 是类型参数定义。如:
public class GenericMethod {
publicstatic <T> T getMiddle(T... a){
returna[a.length/2];
}
}
严格的调用方式:
Stringo=GenericMethod.<String>getMiddle("213","result","12");
一般情况下调用时可以省略,看起来就像定义String类型参数的方法:
GenericMethod.getMiddle(String,String,String),这是因为jdk会根据参数类型进行推断。看一下下面的例子:
Objecto=GenericMethod.getMiddle("213",0,"12");
System.out.println(o.getClass());
System.out.println(o);
输出结果为:
class java.lang.Integer
0
这是因为jdk推断三个参数的共同父类,匹配为Object,那么相当于:
Objecto=GenericMethod.<Object>getMiddle("213",0,"12");
习惯了类型参数放在类的后面,如ArrayList<String>,泛型方法为什么不放在后面?看一个例子:
public static <T,S> Tf(T t){returnt;}
public static class a{}
public static class b{}
//尽量恶心一点
@Test
public void test(){
ac=new a();
<a,b>f(c);//OK
f<a,b>(c);//error,看起来像是一个逗号运算符连接的两个逻辑表达式,当然目前java中除了for(...)并不支持逗号运算符
}
因此,为了避免歧义,jdk采用类型限定符前置。
泛型方法与泛型类的方法
如果泛型方法定义在泛型类中,而且类型参数一样:
public classGenericMethod<T> {
public<T> void sayHi(T t){
System.out.println("Hi"+t);
}
}
是不是说,定义GenericMethod时传了 Integer 类型,sayHi()也就自动变成 Integer 了呢?No。
String i="abc";
newGenericMethod<Integer>().<String>sayHi(i);
该代码运行一点问题都没有。原因就在于泛型方法中的<T>,如果去掉它,就有问题了。
The method sayHi(Integer) inthe typeGenericMethod<Integer> is not applicable for the arguments
(String)
小结:
泛型方法有自己的类型参数,泛型类的成员方法使用的是当前类的类型参数。
方法中有<T> 是泛型方法;没有的,称为泛型类中的成员方法。
类型参数的限定
如果限制只有特定某些类可以传入T参数,那么可以对T进行限定,如:只有实现了特定接口的类:<TextendsComparable>,表示的是Comparable及其子类型。
为什么是extends不是 implements,或者其他限定符?
严格来讲,该表达式意味着:`TsubtypeOf Comparable`,jdk不希望再引入一个新的关键词;
其次,T既可以是类对象也可以是接口,如果是类对象应该是`implements`,而如果是接口,则应该是`extends`;从子类型上来讲,extends更接近要表达的意思。
好吧,这是一个约定。
限定符可以指定多个类型参数,分隔符是 &,不是逗号,因为在类型参数定义中,逗号已经作为多个类型参数的分隔符了,如:<T,S extends Comparable & Serializable>。
泛型限定的优点:
限制某些类型的子类型可以传入,在一定程度上保证类型安全;
可以使用限定类型的方法。如:
public classParent<T>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
//这里只能使用name自object继承的方法
this.name= name;
}
}
加上限定符,就可以访问限定类型的方法了,类型更明确。
public class Parent<TextendsList<T>>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
//这里可以访问List的方法,如name.size()
this.name= name;
}
}
注:
我们知道final类不可继承,在继承机制上class SomeString extendsString是错误的,但泛型限定符使用时是可以的:<T extends String>,只是会给一个警告。
后面的通配符限定有一个super关键字,这里没有。
泛型擦除
泛型只在编译阶段有效,编译后类型被擦除了,也就是说jvm中没有泛型对象,只有普通对象。所以完全可以把代码编译为jdk1.0可以运行的字节码。
擦除的方式
定义部分,即尖括号中间的部分直接擦除。
public classGenericClass<T extendsComparable>{}
擦除后:
public class GenericClass{}
引用部分如:
public T field1;
其中的T被替换成对应的限定类型,擦除后:
public Comparable field1;
如果没有限定类型:
public classGenericClass<T>{
public T field1;
}
那么的替换为object,即:
public class GenericClass{
public Object field1;
}
有多个限定符的,替换为第一个限定类型名。如果引用了第二个限定符的类对象,编译器会在必要的时候进行强制类型转换。
public classGenericClass<T extendsComparable & Serializable>{
public T field1;
}
类擦除后变为:
public class GenericClass{
public Comparablefield1;
}
而表达式返回值返回时,泛型的编译器自动插入强制类型转换。
泛型擦除的残留
反编译GenericClass:
Compiled from"GenericClass.java"
publicclasscom.pollyduan.generic.GenericClass<T> {
public T field1;
publiccom.pollyduan.generic.GenericClass();
}
好像前面说的不对啊,这还是T啊,没有擦除呀?
这就是擦除的残留。反汇编:
{
public T field1;
descriptor:Ljava/lang/Object;
flags: ACC_PUBLIC
Signature: #8 // TT;
publiccom.pollyduan.generic.GenericClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1,locals=1, args_size=1
0:aload_0
1:invokespecial#12 //Method java/lang/Object."<init>":()V
4:return
LineNumberTable:
line2: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/pollyduan/generic/GenericClass;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 5 0 this Lcom/pollyduan/generic/GenericClass<TT;>;
}
SourceFile:"GenericClass.java"
Signature: #22//<T:Ljava/lang/Object;>Ljava/lang/Object;
其中:
descriptor:对方法参数和返回值进行描述;
signature:泛型类中独有的标记,普通类中没有,JDK5才加入,标记了定义时的成员签名,包括定义时的泛型参数列表,参数类型,返回值等;
可以看到public T field1;是签名,还保留了定义的格式;其对应的参数类型是Ljava/lang/Object;。
最后一行是类的签名,可以看到T后面有跟了擦除后的参数类型:<T:Ljava/lang/Object;>。
这样的机制,对于分析字节码是有意义的。
泛型的约束和限制
不能使用8个基本类型实例化类型参数
原因在于类型擦除,Object不能存储基本类型:
byte,char,short,int,long,float,double,boolean
从包装类角度来看,或者说三个:
Number(byte,short,int,long,float,double),char,boolean
类型检查不可使用泛型
if(aaainstanceofPair<String>){}//error
Pair<String> p =(Pair<String>)a;//warn
Pair<String> p;
Pair<Integer> i;
i.getClass()==p.getClass();//true
不能创建泛型对象数组
GenericMethod<User>[]o=null;//ok
o=newGenericMethod<User>[10];//error
可以定义泛型类对象的数组变量,不能创建及初始化。
注,可以创建通配类型数组,然后进行强制类型转换。不过这是类型不安全的。
o=(GenericMethod<User>[])newGenericMethod<?>[10];
不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此jdk强制不允许。
有一个特例是方法的可变参数,虽然本质上是数组,却可以使用泛型。
安全的方法是使用List。
Varargs警告
java不支持泛型类型的对象数组,可变参数是可以的。它也正是利用了强制类型转换,因此同样是类型不安全的。所以这种代码编译器会给一个警告。
public static <T> TgetMiddle(T...a){
return a[a.length/2];
}
去除警告有两种途径:一种是在定义可变参数方法上(本例中的getMiddle())加上@SafeVarargs注解,另一种是在调用该方法时添加@SuppressWarnings("unchecked")注解。
不能实例化泛型对象
T t= new T();//error
T.class.newInstance();//error
T.class;//error
解决办法是传入Class<T> t参数,调用t.newInstance()。
public void sayHi(Class<T>c){
Tt=null;
try{
t=c.newInstance();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("Hi"+t);
}
不能在泛型类的静态域中使用泛型类型
public classSingleton<T>{
privatestatic T singleton; //error
publicstatic T getInstance(){} //error
publicstatic void print(T t){} //error
}
但是,静态的泛型方法可以使用泛型类型:
public static <T> TgetInstance(){returnnull;} //ok
public static <T> voidprint(T t){}//ok
这个原因很多资料中都没说的太明白,说一下个人理解,仅供参考:
1. 泛型类中,<T>称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:`private T t;`,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的。
2. 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T所指的具体类型已经明确了。
不能捕获泛型类型的对象
Throwable类不可以被继承,自然也不可能被catch。
public classGenericThrowable<T>extends Throwable{
//The generic classGenericThrowable<T> may not subclassjava.lang.Throwable
}
但由于Throwable可以用在泛型类型参数中,因此可以变相的捕获泛型的Throwable对象。
@Test
public voidtestGenericThrowable(){
GenericThrowable<RuntimeException>obj=newGenericThrowable<RuntimeException>();
obj.doWork(newRuntimeException("why?"));
}
public static classGenericThrowable<Textends Throwable>{
public void doWork(Tt) throws T{
try{
inti=3/0;
}catch(Throwablecause){
t.initCause(cause);
throwt;
}
}
}
这个能干什么?
@Test
public voidtestGenericThrowable(){
GenericThrowable<RuntimeException>obj=newGenericThrowable<RuntimeException>();
obj.doWork(newRuntimeException("What did you do?"));
}
public static classGenericThrowable<Textends Throwable>{
public void doWork(Tt) throws T{
try{
Readerreader=new FileReader("notfound.txt");
//这里应该是checked异常
}catch(Throwablecause){
t.initCause(cause);
throwt;
}
}
}
FileReader实例化可能抛出已检查异常,jdk中要求必须捕获或者抛出已检查异常。这种模式把它给隐藏了。也就是说可以消除已检查异常,有点不地道,颠覆了java异常处理的认知,后果不可预料,慎用。
擦除的冲突
重载与重写
定义一个普通的父类:
packagecom.pollyduan.generic;
public class Parent{
publicvoid setName(Object name) {
System.out.println("Parent:"+ name);
}
}
那么继承一个子类,Son.java
packagecom.pollyduan.generic;
public class Son extendsParent {
publicvoid setName(String name) {
System.out.println("son:"+ name);
}
publicstatic void main(String[] args) {
Sonson=new Son();
son.setName("abc");
son.setName(newObject());
}
}
Son类重载了一个setName(String)方法,这没问题。输出:
son:abc
Parent:java.lang.Object@6d06d69c
Parent修改泛型类:
packagecom.pollyduan.generic;
public classParent<T>{
publicvoid setName(T name) {
System.out.println("Parent:"+ name);
}
}
从擦除的机制得知,擦除后的class文件为:
packagecom.pollyduan.generic;
public class Parent{
publicvoid setName(Object name) {
System.out.println("Parent:"+ name);
}
}
这和最初的非泛型类是一样的,那么Son类修改为:
packagecom.pollyduan.generic;
public class SonextendsParent<String> {
publicvoid setName(String name) {
System.out.println("son:"+ name);
}
publicstatic void main(String[] args) {
Son son=new Son();
son.setName("abc");
son.setName(newObject());//The method setName(String) in the type Sonis not applicable for thearguments (Object)
}
}
发现重载无效了。这是泛型擦除造成的,无论是否在setName(String)是否标注为@Override都将是重写,都不是重载。而且,即便你不写setName(String)方法,编译器已经默认重写了这个方法。
换一个角度来考虑,定义Son时,Parent已经明确了类型参数为String,那么再写setName(Stirng)是重写,也是合理的。
packagecom.pollyduan.generic;
public class SonextendsParent<String> {
publicstatic void main(String[] args) {
Sonson=new Son();
son.setName("abc");//ok
}
}
反编译会发现,编译器在内部编译了两个方法:
public voidsetName(java.lang.String);
public voidsetName(java.lang.Object);
setName(java.lang.Object) 虽然是public但编码时会发现不可见,它称为"桥方法",它会重写父类的方法。
Son son=new Son();
Parent p=son;
p.setName(new Object());
强行调用会转换异常,也就证明了它实际上调用的是son的setName(String)。
我非要重载怎么办?只能曲线救国,改个名字吧。
public void setName2(Stringname) {
System.out.println("son:"+ name);
}
继承泛型的参数化
一个泛型类的类型参数不同,称之为泛型的不同参数化。
泛型有一个原则:一个类或类型变量不可成为两个不同参数化的接口类型的子类型。如:
packagecom.pollyduan.generic;
import java.util.Comparator;
public class Parentimplements Comparator{
@Override
public intcompare(Object o1, Object o2) {
return0;
}
}
public class Son extendsParent implements Comparator {
}
这样是没有问题的。如果增加了泛型参数化:
package com.pollyduan.generic;
import java.util.Comparator;
public class ParentimplementsComparator<Parent>{
@Override
public intcompare(Parent o1, Parent o2) {
return0;
}
}
packagecom.pollyduan.generic;
import java.util.ArrayList;
import java.util.Comparator;
public class Son extendsParent implements Comparator<Son> {
//The interfaceComparator cannot be implemented more than once withdifferent arguments
}
原因是Son实现了两次Comparator<T>,擦除后均为Comparator<Object>,造成了冲突。
通配符类型
通配符是在泛型类使用时的一种机制,不能用在泛型定义时的泛型表达式中(这是泛型类型参数限定符)。
子类型通配符
如果P是S的超类,那么 Pair<S>就是Pair<? extends P>的子类型,通配符就是为了解决这个问题的。
这称为子类型限定通配符,又称上边界通配符(upperbound wildcardGenerics),代表继承它的所有子类型,通配符匹配的类型不允许作为参数传入,只能作为返回值。
public static void test1() {
Parent<Integer> bean1= new Parent<Integer>();
bean1.setName(123);
Parent<? extendsNumber> bean2 = bean1;
Integer i = 100;
bean2.setName(i);// 编译错误
Number s =bean2.getName();
System.out.println(s);
}
getName()的合理性:
无论bean2指向的是任何类型的对象,只要是Number的子类型,都可以用Number类型变量接收。
为什么setName(str)会抛出异常呢?
1. <? extends Number> 表明了入参是Number的子类型;
2. 那么bean2 可以指向Parent<Integer>,也可以指向Parent<Double>,这都是符合规则的;
3. 再看setName(<? extendsNumber>),逻辑上传入Integer或者Double对象都是符合逻辑的;
4. 如果bean2指向的是Parent<Integer>,而传入的对象是Double的,两个看似合理的规则到一起就不行了。
5. 因此,jdk无法保证类型的安全性,干脆不允许这样——不允许泛型的子类型通配类型作为入参。
超类型通配符
与之对应的是超类型 Pair<?super P>,又称下边界通配符(lowerboundwildcard Generics),通配符匹配的类型可以为方法提供参数,不能得到返回值。
public static void test2(){ public static void test2() {
Parent<Number>bean1 = new Parent<Number>();
bean1.setName(123);
Parent<?super Integer> bean2 = bean1;
Integeri = 100;
bean2.setName(i);
Integers = bean2.getName();// 编译错误
Objecto = bean2.getName();// ok
System.out.println(o);
}
}
setName的可行性:
1. 无论bean2指向Parent<Number>,Parent<Integer>还是Parent<Object>都是允许的;
2. 都可以传入Integer或Integer的子类型。
getName为毛报错?
1. 由于限定类型的超类可能有很多,getName返回类型不可预知,如Integer或其父类型Number/OtherParentClass...都无法保证类型检查的安全。
2. 但是由于Java的所有对象的顶级祖先类都是Object,因此可以用Object获取getName返回值。
无限定通配符
Pair<?> 就是 Pair<? extendsObject>
因此,无限定通配符可以作为返回值,不可做入参。
返回值只能保存在Object中。
P<?> 和P
Pair可以调用setter方法,这是它和Pair<?>最重要的区别。
P<?> 不等于 P<Object>
P<Object>是P<?>的子类。
类型通配符小结
1. 限定通配符总是包括自己;
2. 子类型通配符:set方法受限,只可读,不可写;
3. 超类型通配符:get方法受限,不可读(Object除外),只可写;
4. 无限定通配符,只可读不可写;
5. 如果你既想存,又想取,那就别用通配符;
6. 不可同时声明子类型和超类型限定符,及extends和super只能出现一个。
通配符的受限只针对setter(T)和T getter(),如果定义了一个setter(Integer)这种具体类型参数的方法,无限制。
通配符捕获
通配符限定类中可以使用T,编译器适配类型。
有一个键值对的泛型类:
@Data
class Pair<T> {
privateTkey;
private Tvalue;
}
使用通配类型创建一个swap方法交换key-value,交换时需要先使用一个临时变量保存一个字段:
public static voidswap(Pair<?> p){
// ?k=p.getKey();//error,?不可作为具体类型限定符
Object k=p.getKey();//好吧,换成object,ok
p.setKey(p.getValue());//but,通配符类型不可做入参
p.setValue(k);
}
这里有一个办法解决它,再封装一个swapHelper():
private static <T>voidswapHelper(Pair<T> p){
Tk=p.getKey();
p.setKey(p.getValue());
p.setValue(k);
}
public static voidswap(Pair<?> p){
swapHelper(p);
}
这种方式,称为:通配符捕获,用一个Pair<T>来捕获 Pair<?>中的类型。
注:
当然,你完全可以直接使用swapHelper,这里只是为了说明这样一种捕获机制。
只允许捕获单个、确定的类型,如:ArrayList<Pair<?>>是无法使用ArrayList<Pair<T>> 捕获的。
泛型与继承
继承的原则
继承泛型类时,必须对父类中的类型参数进行初始化。或者说父类中的泛型参数必须在子类中可以确定具体类型。
例如:有一个泛型类Parent<T>,那么Son类定义时有两种方式初始化父类型的类型参数:
1 用具体类型初始化:
public class SonextendsParent<String>{}
2 用子类中的泛型类型初始化父类:
public class Son<T>extendsParent<T>{}
Pair<P>和Pair<S>
无论P和S有什么继承关系,一般Pair<P>和Pair<S>没什么关系。
Pair<Son> s=newPair<>();
Pair<Parent>p=s;//error
Parent<T>和Son<T>
泛型类自身可以继承其他类或实现接口,如List<T>实现ArrayList<T>
泛型类可以扩展泛型类或接口,如ArrayList<T>实现了 List<T>,此时ArrayList<T>可以转换为List<T>。这是安全的。
Parent<T>和Parent
Parent<T>随时都可以转换为原生类型Parent,但需要注意类型检查的安全性。
packagecom.pollyduan.generic;
import java.io.File;
class Parent<T>{
private Tname;
public TgetName() {
returnname;
}
publicvoid setName(T name) {
this.name= name;
}
publicstatic void main(String[] args) {
Parent<String>p1=new Parent<>();
p1.setName("tom");
System.out.println(p1.getName());
Parent p2=p1;
p2.setName(newFile("1.txt"));//严重error
System.out.println(p2.getName());
}
}
运行没有异常,注意。
Person<? extends XXX>
严格讲通配符限定的泛型对象不属于继承范畴,但使用中有类似继承的行为。
Son是Parent的子类型,那么Person<?extendsSon>就是Person<?extendsParent> 的子类型。
Person<? extendsObject> 等同于 Person<?>,那么基于上以规则可以推断:Person<?extendsParent> 是Person<?> 的子类型。
Person<Object> 是 Person<?> 的子类型。
泛型与反射
泛型相关的反射
有了泛型机制,jdk的reflect包中增加了几个泛型有关的类:
Class<T>.getGenericSuperclass()
获取泛型超类
ParameterizedType
类型参数实体类
实例
基于泛型的通用JDBC DAO。
User.java
packagecom.pollyduan.generic;
@Data
public class User {
privateInteger id;
privateString name;
}
AbstractBaseDaoImpl.java
packagecom.pollyduan.generic;
public abstractclassAbstractBaseDaoImpl<T> {
publicAbstractBaseDaoImpl() {
Typet = getClass().getGenericSuperclass();
System.out.println(t);
}
}
UserDaoImpl.java
packagecom.pollyduan.generic;
public class UserDaoImplextendsAbstractBaseDaoImpl<User> {
publicstatic void main(String[] args) {
UserDaoImpluserDao=new UserDaoImpl();
}
}
运行UserDaoImpl.main(),输出:
com.pollyduan.generic.AbstractBaseDaoImpl<com.pollyduan.generic.User>
可以看到,在抽象类AbstractBaseDaoImpl中可以拿到泛型类的具体类。
从这一机制,可以通过AbstractBaseDaoImpl实现通用的JDBA DAO。
完善AbstractBaseDaoImpl.java
packagecom.pollyduan.generic;
importjava.lang.reflect.Field;
importjava.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
importjava.util.stream.Collectors;
public abstractclassAbstractBaseDaoImpl<T, K> {
privateClass<T> entityClass;
privateClass<T> primaryKeyClass;
publicAbstractBaseDaoImpl() {
Typet = getClass().getGenericSuperclass();
ParameterizedTypept = (ParameterizedType) t;
Type[]typeParameters = pt.getActualTypeArguments();
entityClass= (Class<T>) typeParameters[0];
primaryKeyClass= (Class<T>) typeParameters[1];
}
publicvoid save(T t) {
StringBuildersb = new StringBuilder("INSERT INTO ");
sb.append(entityClass.getSimpleName());
sb.append("(");
Field[]fields = entityClass.getDeclaredFields();
StringfieldNames = Arrays.asList(fields).stream().map(x->x.getName()).collect(Collectors.joining(","));
sb.append(fieldNames);
sb.append(")VALUES(");
sb.append(fieldNames.replaceAll("[^,]+","?"));
sb.append(")");
System.out.println(sb.toString());
//根据反射还要遍历fields处理变量绑定,略。
}
publicvoid delete(K k) {
StringBuildersb = new StringBuilder("DELETE FROM ");
sb.append(entityClass.getSimpleName());
sb.append("WHERE ID=?");// 这里默认主键名为id,应该配合注解动态获取主键名
System.out.println(sb.toString());
}
publicvoid update(T t) {
StringBuildersb = new StringBuilder("UPDATE ");
sb.append(entityClass.getSimpleName());
sb.append("SET ");
Field[]fields = entityClass.getDeclaredFields();
for(int i = 0; i < fields.length; i++) {
if(fields[i].getName().toLowerCase().equals("id")) {
continue;
}
sb.append(fields[i].getName());
sb.append("=?");
if(i < fields.length - 1) {
sb.append(",");
}
}
sb.append("WHERE ID=?");
System.out.println(sb.toString());
}
public Tget() throws Exception {
Tt = null;
//模拟resultset
Map<String,Object> rs = new HashMap<>();
t= entityClass.newInstance();
Field[]fields = entityClass.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
field.set(t,rs.get(field.getName()));
}
returnt;
}
public static voidmain(String[] args) {
UserDaoImpluserDao=new UserDaoImpl();
Useruser1=new User();
userDao.save(user1);
userDao.delete(1);
userDao.update(user1);
try {
Useruser2=userDao.get();
System.out.println(user2);
}catch(Exception e) {
e.printStackTrace();
}
}
}
有现成的ORM框架可用,这里就意思意思得了。输出:
INSERT INTO User(id,name)VALUES(?,?)
DELETE FROM User WHERE ID=?
UPDATE User SET name=? WHEREID=?
User(id=1, name=Peter)
阅读全文
0 0
- Java泛型总结
- Java泛型总结
- JAVA泛型总结
- Java泛型总结
- java 泛型总结
- java泛型总结
- JAVA泛型总结
- JAVA泛型总结
- Java-泛型总结
- java泛型总结
- java 泛型总结
- java泛型总结
- Java泛型总结
- Java泛型总结
- Java泛型总结
- java泛型总结
- Java泛型总结
- Java泛型总结
- springboot 定时任务
- 我想我们都没太在意
- 每日一题(36)—— 什么是预编译 , 何时需要预编译?
- Spring aop 原理及各种应用场景
- python实例(复制列表)
- Java泛型总结
- emWin & STemWin & uCGUI 的中文支持
- Topological Sorting(拓扑排序必考题)
- 高精度三维空间测量、定位与追踪(上)
- ToolBar使用详解
- 隐式转换与显示转换的区别概念理解
- Response
- mui与vue结合 功能网址
- HTML初识