zoukankan      html  css  js  c++  java
  • Java泛型总结——吃透泛型开发

    什么是泛型

    泛型是jdk5引入的类型机制,就是将类型参数化,它是早在1999年就制定的jsr14的实现。

    泛型机制将类型转换时的类型检查从运行时提前到了编译时,使用泛型编写的代码比杂乱的使用object并在需要时再强制类型转换的机制具有更好的可读性和安全性。

    泛型程序设计意味着程序可以被不同类型的对象重用,类似c++的模版。

    泛型对于集合类尤其有用,如ArrayList。这里可能有疑问,既然泛型为了适应不同的对象,ArrayList本来就可以操作不同类型的对象呀?那是因为没有泛型之前采用继承机制实现的,实际上它只维护了一个Object对象的数组。结果就是对List来说它只操作了一类对象Object,而在用户看来却可以保存不同的对象。

    泛型提供了更好的解决办法——类型参数,如:

    List<String> list = new ArrayList<String>();

    这样解决了几个问题:

    1 可读性,从字面上就可以判断集合中的内容类型;
    2 类型检查,避免插入非法类型。
    3 获取数据时不在需要强制类型转换。

    泛型类

    public class Pair<T>{
        private T field1;
    }

    其中 <T> 是类型参数定义。

    使用时:Pair<String> p = new Pair<String>();

    此时类内部的field1就是字符串类型了。

    如果引用多个类型,可以使用逗号分隔:<S, D>

    类型参数名可以使用任意字符串,建议使用有代表意义的单个字符,以便于和普通类型名区分,如:T代表type,有原数据和目的数据就用SD,子元素类型用E等。当然,你也可以定义为XYZ,甚至xyZ

    泛型方法

    泛型方法定义如下:

    public static <T> T marshalle(T arg){}

    与泛型类一样,<T> 是类型参数定义。如:

    public class GenericMethod {
        public static <T> T getMiddle(T... a){
            return a[a.length/2];
        }
    }

    严格的调用方式:

    String o=GenericMethod.<String>getMiddle("213","result","12");

    一般情况下调用时可以省略,看起来就像定义String类型参数的方法:
    GenericMethod.getMiddle(String,String,String),这是因为jdk会根据参数类型进行推断。看一下下面的例子:

    Object o=GenericMethod.getMiddle("213",0,"12");
    System.out.println(o.getClass());
    System.out.println(o);

    输出结果为:

    class java.lang.Integer
    0

    这是因为jdk推断三个参数的共同父类,匹配为Object,那么相当于:

    Object o=GenericMethod.<Object>getMiddle("213",0,"12");

    习惯了类型参数放在类的后面,如ArrayList<String>,泛型方法为什么不放在后面?看一个例子:

    public static <T,S> T f(T t){return t;}
    public static class a{}
    public static class b{}
    //尽量恶心一点
    
    @Test
    public void test(){
      a c=new a();
        <a,b>f(c);//OK
      f<a,b>(c);//error,看起来像是一个逗号运算符连接的两个逻辑表达式,当然目前java中除了for(...)并不支持逗号运算符
    }

    因此,为了避免歧义,jdk采用类型限定符前置。

    泛型方法与泛型类的方法

    如果泛型方法定义在泛型类中,而且类型参数一样:

    public class GenericMethod<T> {
        public <T> void sayHi(T t){
            System.out.println("Hi "+t);
        }
    }

    是不是说,定义GenericMethod时传了 Integer 类型,sayHi()也就自动变成 Integer 了呢?No。

    String i="abc";
    new GenericMethod<Integer>().<String>sayHi(i);

    该代码运行一点问题都没有。原因就在于泛型方法中的<T>,如果去掉它,就有问题了。

    The method sayHi(Integer) in the type GenericMethod<Integer> is not applicable for the arguments
     (String)

    小结:

    泛型方法有自己的类型参数,泛型类的成员方法使用的是当前类的类型参数。

    方法中有<T> 是泛型方法;没有的,称为泛型类中的成员方法。

    类型参数的限定

    如果限制只有特定某些类可以传入T参数,那么可以对T进行限定,如:只有实现了特定接口的类:<T extends Comparable>,表示的是Comparable及其子类型。

    为什么是extends不是 implements,或者其他限定符?

    严格来讲,该表达式意味着:`T subtypeOf Comparable`,jdk不希望再引入一个新的关键词;
    
    其次,T既可以是类对象也可以是接口,如果是类对象应该是`implements`,而如果是接口,则应该是`extends`;从子类型上来讲,extends更接近要表达的意思。
    
    好吧,这是一个约定。

    限定符可以指定多个类型参数,分隔符是 &,不是逗号,因为在类型参数定义中,逗号已经作为多个类型参数的分隔符了,如:<T,S extends Comparable & Serializable>

    泛型限定的优点:

    限制某些类型的子类型可以传入,在一定程度上保证类型安全;

    可以使用限定类型的方法。如:

    public class Parent<T>{
        private T name;
    
        public T getName() {
            return name;
        }
    
        public void setName(T name) {
            //这里只能使用name自object继承的方法
            this.name = name;
        }
    }

    加上限定符,就可以访问限定类型的方法了,类型更明确。

    public class Parent<T extends List<T>>{
        private T name;
    
        public T getName() {
            return name;
        }
    
        public void setName(T name) {
            //这里可以访问List的方法,如name.size()
            this.name = name;
        }
    }

    注:

    我们知道final类不可继承,在继承机制上class SomeString extends String是错误的,但泛型限定符使用时是可以的:<T extends String>,只是会给一个警告。

    后面的通配符限定有一个super关键字,这里没有。

    泛型擦除

    泛型只在编译阶段有效,编译后类型被擦除了,也就是说jvm中没有泛型对象,只有普通对象。所以完全可以把代码编译为jdk1.0可以运行的字节码。

    擦除的方式

    定义部分,即尖括号中间的部分直接擦除。

    public class GenericClass<T extends Comparable>{}

    擦除后:

    public class GenericClass{}

    引用部分如:

    public T field1;

    其中的T被替换成对应的限定类型,擦除后:

    public Comparable field1;

    如果没有限定类型:

    public class GenericClass<T>{
      public T field1;
    }

    那么的替换为object,即:

    public class GenericClass{
      public Object field1;
    }

    有多个限定符的,替换为第一个限定类型名。如果引用了第二个限定符的类对象,编译器会在必要的时候进行强制类型转换。

    public class GenericClass<T extends Comparable & Serializable>{
      public T field1;
    }

    类擦除后变为:

    public class GenericClass{
      public Comparable field1;
    }

    而表达式返回值返回时,泛型的编译器自动插入强制类型转换。

    泛型擦除的残留

    反编译GenericClass:

    Compiled from "GenericClass.java"
    public class com.pollyduan.generic.GenericClass<T> {
      public T field1;
      public com.pollyduan.generic.GenericClass();
    }

    好像前面说的不对啊,这还是T啊,没有擦除呀?

    这就是擦除的残留。反汇编:

    {
    public T field1;
      descriptor: Ljava/lang/Object;
      flags: ACC_PUBLIC
      Signature: #8 // TT;
    
    public com.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:
          line 2: 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(aaa instanceof Pair<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=new GenericMethod<User>[10];//error

    可以定义泛型类对象的数组变量,不能创建及初始化。

    注,可以创建通配类型数组,然后进行强制类型转换。不过这是类型不安全的。

    o=(GenericMethod<User>[]) new GenericMethod<?>[10];

    不可以创建的原因是:因为类型擦除的原因无法在为元素赋值时类型检查,因此jdk强制不允许。

    有一个特例是方法的可变参数,虽然本质上是数组,却可以使用泛型。

    安全的方法是使用List。

    Varargs警告

    java不支持泛型类型的对象数组,可变参数是可以的。它也正是利用了强制类型转换,因此同样是类型不安全的。所以这种代码编译器会给一个警告。

    public static <T> T getMiddle(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){
      T t=null;
      try {
        t=c.newInstance();
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println("Hi "+t);
    }

    不能在泛型类的静态域中使用泛型类型

    public class Singleton<T>{
        private static T singleton; //error
        public static T getInstance(){} //error
        public static void print(T t){} //error
    }

    但是,静态的泛型方法可以使用泛型类型:

    public static <T> T getInstance(){return null;} //ok
    public static <T> void print(T t){} //ok

    这个原因很多资料中都没说的太明白,说一下个人理解,仅供参考:

    1. 泛型类中,<T>称为类型变量,实际上就相当于在类中隐形的定义了一个不可见的成员变量:`private T t;`,这是对象级别的,对于泛型类型变量来说是在对象初始化时才知道其具体类型的。而在静态域中,不需要对象初始化就可以调用,这是矛盾的。
    
    2. 静态的泛型方法,是在方法层面定义的,就是说在调用方法时,T所指的具体类型已经明确了。

    不能捕获泛型类型的对象

    Throwable类不可以被继承,自然也不可能被catch

    public class GenericThrowable<T> extends Throwable{
      //The generic class GenericThrowable<T> may not subclass java.lang.Throwable
    }

    但由于Throwable可以用在泛型类型参数中,因此可以变相的捕获泛型的Throwable对象。

    @Test
    public void testGenericThrowable(){
      GenericThrowable<RuntimeException> obj=new GenericThrowable<RuntimeException>();
      obj.doWork(new RuntimeException("why?"));
    }
    
    public static class GenericThrowable<T extends Throwable>{
      public void doWork(T t) throws T{
        try{
          int i=3/0;
        }catch(Throwable cause){
          t.initCause(cause);
          throw t;
        }
      }
    }

    这个能干什么?

    @Test
    public void testGenericThrowable(){
      GenericThrowable<RuntimeException> obj=new GenericThrowable<RuntimeException>();
      obj.doWork(new RuntimeException("What did you do?"));
    }
    public static class GenericThrowable<T extends Throwable>{
      public void doWork(T t) throws T{
        try{
          Reader reader=new FileReader("notfound.txt");
          //这里应该是checked异常
        }catch(Throwable cause){
          t.initCause(cause);
          throw t;
        }
      }
    }

    FileReader实例化可能抛出已检查异常,jdk中要求必须捕获或者抛出已检查异常。这种模式把它给隐藏了。也就是说可以消除已检查异常,有点不地道,颠覆了java异常处理的认知,后果不可预料,慎用。

    擦除的冲突

    重载与重写

    定义一个普通的父类:

    package com.pollyduan.generic;
    
    public class Parent{
    
        public void setName(Object name) {
            System.out.println("Parent:" + name);
        }
    }

    那么继承一个子类,Son.java

    package com.pollyduan.generic;
    
    public class Son extends Parent {
        public void setName(String name) {
            System.out.println("son:" + name);
        }
    
        public static void main(String[] args) {
            Son son=new Son();
            son.setName("abc");
            son.setName(new Object());
        }
    }

    Son类重载了一个setName(String)方法,这没问题。输出:

    son:abc
    Parent:java.lang.Object@6d06d69c

    Parent修改泛型类:

    package com.pollyduan.generic;
    
    public class Parent<T>{
    
        public void setName(T name) {
            System.out.println("Parent:" + name);
        }
    }

    从擦除的机制得知,擦除后的class文件为:

    package com.pollyduan.generic;
    
    public class Parent{
    
        public void setName(Object name) {
            System.out.println("Parent:" + name);
        }
    }

    这和最初的非泛型类是一样的,那么Son类修改为:

    package com.pollyduan.generic;
    
    public class Son extends Parent<String>  {
        public void setName(String name) {
            System.out.println("son:" + name);
        }
    
        public static void main(String[] args) {
            Son son=new Son();
            son.setName("abc");
            son.setName(new Object());//The method setName(String) in the type Son is not applicable for the arguments (Object)
        }
    }

    发现重载无效了。这是泛型擦除造成的,无论是否在setName(String)是否标注为@Override都将是重写,都不是重载。而且,即便你不写setName(String)方法,编译器已经默认重写了这个方法。

    换一个角度来考虑,定义Son时,Parent已经明确了类型参数为String,那么再写setName(Stirng)是重写,也是合理的。

    package com.pollyduan.generic;
    
    public class Son extends Parent<String>  {
    
        public static void main(String[] args) {
            Son son=new Son();
            son.setName("abc");//ok
        }
    }

    反编译会发现,编译器在内部编译了两个方法:

      public void setName(java.lang.String);
      public void setName(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(String name) {
            System.out.println("son:" + name);
        }

    继承泛型的参数化

    一个泛型类的类型参数不同,称之为泛型的不同参数化。

    泛型有一个原则:一个类或类型变量不可成为两个不同参数化的接口类型的子类型。如:

    package com.pollyduan.generic;
    
    import java.util.Comparator;
    
    public class Parent implements Comparator{
    
        @Override
        public int compare(Object o1, Object o2) {
            return 0;
        }
    }
    
    public class Son extends Parent  implements Comparator   {
    }

    这样是没有问题的。如果增加了泛型参数化:

    package com.pollyduan.generic;
    
    import java.util.Comparator;
    
    public class Parent implements Comparator<Parent>{
    
        @Override
        public int compare(Parent o1, Parent o2) {
            return 0;
        }
    }
    
    package com.pollyduan.generic;
    
    import java.util.ArrayList;
    import java.util.Comparator;
    
    public class Son extends Parent  implements Comparator<Son>   {
      //The interface Comparator cannot be implemented more than once with different arguments
    }

    原因是Son实现了两次Comparator<T>,擦除后均为Comparator<Object>,造成了冲突。

    通配符类型

    通配符是在泛型类使用时的一种机制,不能用在泛型定义时的泛型表达式中(这是泛型类型参数限定符)。

    子类型通配符

    如果P是S的超类,那么 Pair<S>就是Pair<? extends P>的子类型,通配符就是为了解决这个问题的。

    这称为子类型限定通配符,又称上边界通配符(upper bound wildcard Generics),代表继承它的所有子类型,通配符匹配的类型不允许作为参数传入,只能作为返回值。

    public static void test1() {
      Parent<Integer> bean1 = new Parent<Integer>();
      bean1.setName(123);
    
      Parent<? extends Number> 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(<? extends Number>),逻辑上传入Integer或者Double对象都是符合逻辑的;
    4. 如果bean2指向的是Parent<Integer>,而传入的对象是Double的,两个看似合理的规则到一起就不行了。
    5. 因此,jdk无法保证类型的安全性,干脆不允许这样——不允许泛型的子类型通配类型作为入参。

    超类型通配符

    与之对应的是超类型 Pair<? super P>,又称下边界通配符(lower bound wildcard Generics),通配符匹配的类型可以为方法提供参数,不能得到返回值。

    public static void test2() {    public static void test2() {
            Parent<Number> bean1 = new Parent<Number>();
            bean1.setName(123);
    
            Parent<? super Integer> bean2 = bean1;
            Integer i = 100;
            bean2.setName(i);
            Integer s = bean2.getName();// 编译错误
            Object o = bean2.getName();// ok
            System.out.println(o);
        }
    }

    setName的可行性:

    1. 无论bean2指向Parent<Number>,Parent<Integer>还是Parent<Object>都是允许的;
    2. 都可以传入IntegerInteger的子类型。

    getName为毛报错?

    1. 由于限定类型的超类可能有很多,getName返回类型不可预知,如Integer 或其父类型Number/OtherParentClass...都无法保证类型检查的安全。
    
    2. 但是由于Java的所有对象的顶级祖先类都是Object,因此可以用Object获取getName返回值。

    无限定通配符

    Pair<?> 就是 Pair<? extends Object>

    因此,无限定通配符可以作为返回值,不可做入参。

    返回值只能保存在Object中。

    P<?> 和P

    Pair可以调用setter方法,这是它和Pair<?>最重要的区别。

    P<?> 不等于 P<Object>

    P<Object>P<?>的子类。

    类型通配符小结

    1. 限定通配符总是包括自己;
    2. 子类型通配符:set方法受限,只可读,不可写;
    3. 超类型通配符:get方法受限,不可读(Object除外),只可写;
    4. 无限定通配符,只可读不可写;
    5. 如果你既想存,又想取,那就别用通配符;
    6. 不可同时声明子类型和超类型限定符,及extendssuper只能出现一个。

    通配符的受限只针对setter(T)T getter(),如果定义了一个setter(Integer)这种具体类型参数的方法,无限制。

    通配符捕获

    通配符限定类中可以使用T,编译器适配类型。

    有一个键值对的泛型类:

    @Data
    class Pair<T> {
        private T key;
        private T value;
    }

    使用通配类型创建一个swap方法交换key-value,交换时需要先使用一个临时变量保存一个字段:

    public static void swap(Pair<?> p){
    //      ? k=p.getKey();//error,?不可作为具体类型限定符
      Object k=p.getKey();//好吧,换成object,ok
      p.setKey(p.getValue());//but,通配符类型不可做入参
      p.setValue(k);
    }

    这里有一个办法解决它,再封装一个swapHelper():

    private static <T> void swapHelper(Pair<T> p){
      T k=p.getKey();
      p.setKey(p.getValue());
      p.setValue(k);
    }
    public static void swap(Pair<?> p){
      swapHelper(p);
    }

    这种方式,称为:通配符捕获,用一个Pair<T> 来捕获 Pair<?>中的类型。

    注:

    当然,你完全可以直接使用swapHelper,这里只是为了说明这样一种捕获机制。
    
    只允许捕获单个、确定的类型,如:ArrayList<Pair<?>> 是无法使用 ArrayList<Pair<T>> 捕获的。

    泛型与继承

    继承的原则

    继承泛型类时,必须对父类中的类型参数进行初始化。或者说父类中的泛型参数必须在子类中可以确定具体类型。

    例如:有一个泛型类Parent<T>,那么Son类定义时有两种方式初始化父类型的类型参数:

    1 用具体类型初始化:

    public class Son extends Parent<String>{}

    2 用子类中的泛型类型初始化父类:

    public class Son<T> extends Parent<T>{}

    Pair<P>Pair<S>

    无论P和S有什么继承关系,一般Pair<P>Pair<S>没什么关系。

    Pair<Son> s=new Pair<>();
    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,但需要注意类型检查的安全性。

    package com.pollyduan.generic;
    
    import java.io.File;
    
    class Parent<T> {  
        private T name;  
        public T getName() {  
            return name;  
        }  
        public void setName(T name) {  
            this.name = name;  
        }  
    
        public static void main(String[] args) {
            Parent<String> p1=new Parent<>();
            p1.setName("tom");
            System.out.println(p1.getName());
            Parent p2=p1;
            p2.setName(new File("1.txt"));//严重error
            System.out.println(p2.getName());
        }
    }

    运行没有异常,注意。

    Person<? extends XXX>

    严格讲通配符限定的泛型对象不属于继承范畴,但使用中有类似继承的行为。

    SonParent的子类型,那么Person<? extends Son>就是Person<? extends Parent> 的子类型。

    Person<? extends Object> 等同于 Person<?>,那么基于上以规则可以推断:Person<? extends Parent> 是 Person<?> 的子类型。

    Person<Object> 是 Person<?> 的子类型。

    泛型与反射

    泛型相关的反射

    有了泛型机制,jdk的reflect包中增加了几个泛型有关的类:

    Class<T>.getGenericSuperclass()
    
    获取泛型超类
    
    ParameterizedType
    
    类型参数实体类

    实例

    基于泛型的通用JDBC DAO。

    User.java

    package com.pollyduan.generic;
    
    @Data
    public class User {
        private Integer id;
        private String name;
    }

    AbstractBaseDaoImpl.java

    package com.pollyduan.generic;
    
    public abstract class AbstractBaseDaoImpl<T> {
        public AbstractBaseDaoImpl() {
            Type t = getClass().getGenericSuperclass();
            System.out.println(t);
        }
    }

    UserDaoImpl.java

    package com.pollyduan.generic;
    
    public class UserDaoImpl extends AbstractBaseDaoImpl<User> {
        public static void main(String[] args) {
            UserDaoImpl userDao=new UserDaoImpl();
        }
    }

    运行UserDaoImpl.main(),输出:

    com.pollyduan.generic.AbstractBaseDaoImpl<com.pollyduan.generic.User>

    可以看到,在抽象类AbstractBaseDaoImpl中可以拿到泛型类的具体类。

    从这一机制,可以通过AbstractBaseDaoImpl实现通用的JDBA DAO。

    完善AbstractBaseDaoImpl.java

    package com.pollyduan.generic;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public abstract class AbstractBaseDaoImpl<T, K> {
        private Class<T> entityClass;
        private Class<T> primaryKeyClass;
    
        public AbstractBaseDaoImpl() {
            Type t = getClass().getGenericSuperclass();
            ParameterizedType pt = (ParameterizedType) t;
            Type[] typeParameters = pt.getActualTypeArguments();
            entityClass = (Class<T>) typeParameters[0];
            primaryKeyClass = (Class<T>) typeParameters[1];
        }
    
        public void save(T t) {
            StringBuilder sb = new StringBuilder("INSERT INTO ");
            sb.append(entityClass.getSimpleName());
    
            sb.append("(");
            Field[] fields = entityClass.getDeclaredFields();
            String fieldNames = 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处理变量绑定,略。
        }
    
        public void delete(K k) {
            StringBuilder sb = new StringBuilder("DELETE FROM ");
            sb.append(entityClass.getSimpleName());
            sb.append(" WHERE ID=?");// 这里默认主键名为id,应该配合注解动态获取主键名
            System.out.println(sb.toString());
        }
    
        public void update(T t) {
            StringBuilder sb = 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 T get() throws Exception {
            T t = null;
            // 模拟resultset
            Map<String, Object> rs = newHashMap<>();
            t = entityClass.newInstance();Field[] fields = entityClass.getDeclaredFields();for(Field field : fields){
                field.setAccessible(true);
                field.set(t, rs.get(field.getName()));}return t;}publicstaticvoid main(String[] args){UserDaoImpl userDao=newUserDaoImpl();User user1=newUser();
        userDao.save(user1);
        userDao.delete(1);
        userDao.update(user1);try{User user2=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=? WHERE ID=?
    User(id=1, name=Peter)



  • 相关阅读:
    Atitit s2018.6 s6 doc list on com pc.docx Atitit s2018.6 s6 doc list on com pc.docx  Aitit algo fix 算法系列补充.docx Atiitt 兼容性提示的艺术 attilax总结.docx Atitit 应用程序容器化总结 v2 s66.docx Atitit file cms api
    Atitit s2018.5 s5 doc list on com pc.docx  v2
    Atitit s2018.5 s5 doc list on com pc.docx  Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 18821766710 attilax main num.docx Atiitt put post 工具 开发工具dev tool test.docx Atiitt 腾讯图像分类相册管家.docx
    Atitit s2018 s4 doc list dvchomepc dvccompc.docx .docx s2018 s4 doc compc dtS44 s2018 s4 doc dvcCompc dtS420 s2018 s4f doc homepc s2018 s4 doc compc dtS44(5 封私信 _ 44 条消息)WebSocket 有没有可能取代 AJAX
    Atitit s2018 s3 doc list alldvc.docx .docx s2018 s3f doc compc s2018 s3f doc homepc sum doc dvcCompc dtS312 s2018 s3f doc compcAtitit PathUtil 工具新特性新版本 v8 s312.docx s2018 s3f doc compcAtitit 操作日
    Atitit s2018.2 s2 doc list on home ntpc.docx  Atiitt uke制度体系 法律 法规 规章 条例 国王诏书.docx Atiitt 手写文字识别 讯飞科大 语音云.docx Atitit 代码托管与虚拟主机.docx Atitit 企业文化 每日心灵 鸡汤 值班 发布.docx Atitit 几大研发体系对比 Stage-Gat
    Atitit 文员招募规范 attilax总结
    Atitit r2017 r6 doc list on home ntpc.docx
    atitit r9 doc on home ntpc .docx
    Atitit.如何文章写好 论文 文章 如何写好论文 技术博客 v4
  • 原文地址:https://www.cnblogs.com/banxian-yi/p/10579484.html
Copyright © 2011-2022 走看看