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,擦除后均为Comparator,造成了冲突。

    通配符类型

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

    子类型通配符

    假设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

    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. 不可同一时候声明子类型和超类型限定符,即extends和super仅仅能出现一个。

    通配符的受限仅仅针对setter(T)T getter(),假设定义了一个setter(Integer)这样的详细类型參数的方法。无限制。如:假设添加一个方法setId(Integer id),能够随意调用。

    通配符捕获

    通配符限定类中能够使用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实现ArrayList

    泛型类能够扩展泛型类或接口,如ArrayList 实现了 List,此时ArrayList能够转换为List。这是安全的。

    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"));
            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
    
    类型參数实体类

    实例

    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 = new HashMap<>(); t = entityClass.newInstance(); Field[] fields = entityClass.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); field.set(t, rs.get(field.getName())); } return t; } public static void main(String[] args) { UserDaoImpl userDao=new UserDaoImpl(); User user1=new User(); 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)

    有问题可加Q群讨论:9040323

  • 相关阅读:
    小tips: HTTP 请求过程示例图及名称解释
    小tips:使用vuecli2脚手架配置vant自定义主题
    axios使用备忘录
    知乎问题:为什么很多web项目还是使用 px,而不是 rem?
    小tips:HTML5的ruby标签实现给汉字加拼音、details标签实现折叠面板、原生进度条progress和度量meter
    ES6之常用开发知识点:入门(一)
    GitBook相关使用以及配置笔记
    小tips:使用vue-cli脚手架搭建项目,关于eslint语法检测配置
    小tips:JS/CSS实现字符串单词首字母大写
    vue动态子组件的实现方式
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8666120.html
Copyright © 2011-2022 走看看