為了賬號安全,請及時綁定郵箱和手機立即綁定

Java泛型總結——吃透泛型開發

2017.05.24 12:03 15668瀏覽
什么是泛型

泛型是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. 都可以傳入Integer或Integer的子類型。

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)這種具體類型參數的方法,無限制。

通配符捕獲

通配符限定類中可以使用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 = 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)
點擊查看更多內容
33人點贊

若覺得本文不錯,就分享一下吧!

評論

相關文章推薦

正在加載中
意見反饋 幫助中心 APP下載
官方微信

舉報

0/150
提交
取消
lpl竞猜