Java 泛型(泛型類、泛型接口以及泛型方法)

泛型的概念

  • 所謂泛型姨拥,就是允許在定義類绅喉、接口時通過一個標(biāo)識表示類中某個屬性的類型或者是某個方法的返 回值及參數(shù)類型。這個類型參數(shù)將在使用時(例如叫乌,繼承或?qū)崿F(xiàn)這個接口柴罐,用這個類型聲明變量、 創(chuàng)建對象時確定(即傳入實(shí)際的類型參數(shù)憨奸,也稱為類型實(shí)參)革屠。
  • Java 5以后,Java引入了“參數(shù)化類型(Parameterized type)”的概念排宰,允許我們在創(chuàng)建集合時再指定集合元素的類型似芝,正如:List<String>,這表明該List只能保存字符串類型的對象板甘。
  • Java 5改寫了集合框架中的全部接口和類党瓮,為這些接口、類增加了泛型支持盐类,從而可以在聲明集合變量寞奸、創(chuàng)建集合對象時傳入類型實(shí)參。

泛型的引入背景

集合容器類在設(shè)計階段/聲明階段不能確定這個容器到底實(shí)際存的是什么類型的對象在跳,所以在Java 5之前只能把元素類型設(shè)計為Object蝇闭,Java 5之后使用泛型來解決。因?yàn)檫@個時候除了元素的類型不確定硬毕,其他的部分是確定的,例如關(guān)于這個元素如何保存礼仗,如何管理等是確定的吐咳,因此此時把元素的類型設(shè)計成一個參數(shù),這個類型參數(shù)叫做泛型元践。Collection<E>韭脊,List<E>ArrayList<E> 這個<E>就是類型參數(shù)单旁,即泛型沪羔。

引入泛型的目的

  1. 解決元素存儲的安全性問題,好比商品、藥品標(biāo)簽蔫饰,不會弄錯琅豆。
  2. 解決獲取數(shù)據(jù)元素時,需要類型強(qiáng)制轉(zhuǎn)換的問題篓吁,好比不用每回拿商品茫因、藥品都要辨別。

Java泛型可以保證如果程序在編譯時沒有發(fā)岀警告杖剪,運(yùn)行時就不會產(chǎn)生ClassCastException異常冻押。同時,代碼更加簡潔盛嘿、健壯洛巢。

泛型類

先看一個只能持有單個對象的類:

class Student {}
// 操作student表
public class Dao {// 表的共性操作,簡單的增刪改查
    // 添加一條記錄
    public void add(Student s){}
}

如果這時候需要操作teacher表次兆,同樣需要添加一條記錄稿茉,那么這個時候就需要重新編寫Dao類,這個類的可復(fù)用性就不高类垦,如果有很多表需要進(jìn)行這樣的操作狈邑,那么我們就需要為每一張表都單獨(dú)編寫一個新的Dao類。

在Java 5之前蚤认,要解決這個問題米苹,我們可以使用Object

public class Dao {// 表的共性操作,簡單的增刪改查
    // 添加一條記錄
    public void add(Object obj){}
}

這樣又有了新問題砰琢,如果我現(xiàn)在向student表插入一條Teacher的信息蘸嘶,這也是可行了,并且不會報錯陪汽,因?yàn)?code>Object類是所有類的父類训唱。演示一下:

class Student { }

class Teacher { }

public class Dao { // 操作學(xué)生表的Dao
    List<Object> stuList = new ArrayList<>();
    // 向數(shù)據(jù)源中插入一條信息
    public void add(Object obj) {

        stuList.add(obj);
    }

    // 從數(shù)據(jù)源中取出數(shù)據(jù)
    public Object get(int index) {
        return stuList.get(index);
    }

    // 方便起見,直接在Dao類中寫單元測試方法挚冤,實(shí)際開發(fā)中不要這么寫
    @Test
    public void test() {
        Dao stuDao = new Dao();
        Student stu = new Student();
        stuDao.add(stu); // 插入學(xué)生信息

        Teacher teacher = new Teacher();
        stuDao.add(teacher); // 沒有報錯况增,同樣可以插入
        stuDao.add("垃圾數(shù)據(jù)"); // 同樣沒有報錯,這個問題就比較嚴(yán)重了训挡,什么類型的數(shù)據(jù)都能插入

        // 取出數(shù)據(jù)
        Student student = (Student) stuDao.get(0); // 需要強(qiáng)制類型轉(zhuǎn)換
        Teacher teacher1 = (Teacher) stuDao.get(1); // 說出來你可能不信澳骤,我從學(xué)生表中取出了一個老師,混入了一個澜薄?为肮??
        String str = (String) stuDao.get(2); // 我還從學(xué)生表里取出了一個字符串肤京?颊艳?

        System.out.println(student);
        System.out.println(teacher1);
        System.out.println(str);
    }
}

我通過Dao這個類操作數(shù)據(jù)源中的student表,然后分別向其中插入了StudentTeacher棋枕、String三種類型的數(shù)據(jù)白修,這明顯是不符合要求的,student表中的數(shù)據(jù)都被污染了戒悠,這對數(shù)據(jù)來說熬荆,是不安全的,沒有類型限制绸狐,我們很難保證永遠(yuǎn)不會寫錯卤恳,泛型的目的之一就是用來約定集合要存儲什么類型的對象,并通過編譯器確保條件滿足寒矿。

與其使用Object突琳,我們更希望先指定一個類型占位符,稍后再決定具體使用什么類型符相。要達(dá)到這個目的拆融,需要使用類型參數(shù),用尖括號括住啊终,放在類名后面镜豹。然后在使用這個類時,再用實(shí)際的類型替換此類型參數(shù)蓝牲。

T趟脂、KV都可以表示類型參數(shù)例衍,任意字母都可以昔期。類型常用T表示,是Type的簡寫佛玄,KeyValue常用KV表示硼一。

現(xiàn)在將上面的Dao修改一下,T 表示類型參數(shù)(只能是對象類型梦抢,基本數(shù)據(jù)類型不行):

public class Dao<T> {
    List<T> list = new ArrayList<>();
    public void add(T t) {
        list.add(t);
    }

    public T get(int index) {
        return list.get(index);
    }
    
    @Test
    public void test1() {
        Dao<Student> stuDao = new Dao<>(); //在創(chuàng)建對象時般贼,指定參數(shù)類型
        Student stu = new Student();
        stuDao.add(stu); // 插入學(xué)生信息

        //Teacher teacher = new Teacher();
        //stuDao.add(teacher); // 報錯 會主動進(jìn)行類型校驗(yàn)
        //stuDao.add("垃圾數(shù)據(jù)"); // 報錯 
        
        Student student = stuDao.get(0); // 無需進(jìn)行類型轉(zhuǎn)換
    }
}

創(chuàng)建Dao對象時,必須指明T的類型奥吩,就像例子中T的類型為Student具伍,然后,只能在Dao中存儲該類型(或其子類圈驼,因?yàn)槎鄳B(tài)和泛型不沖突)的對象了。當(dāng)你調(diào)用get(int index)取值時望几,直接就是正確的類型绩脆。

Java泛型的核心概念:你只需要告訴編譯器要使用什么類型,剩下的細(xì)節(jié)交給它來處理。

在Java 5中靴迫,創(chuàng)建Dao對象時惕味,你必須這么寫:

Dao<Student> stuDao = new Dao<Student>();

但是在Java 7及之后的版本,就可以這么寫:

Dao<Student> stuDao = new Dao<>(); //類型推斷

泛型接口

定義一個泛型接口:

public interface Dao<T> { // 與泛型類相似玉锌,在類名后面添加<T>名挥,參數(shù)類型

    void add(T t);

    T get(int index);

}

實(shí)現(xiàn)類:

class Student {

}

public class StudentDaoImpl implements Dao<Student> {

    List<Student> stuList = new ArrayList<>();
    @Override
    public void add(Student student) {
        stuList.add(student);
    }

    @Override
    public Student get(int index) {
        return stuList.get(index);
    }

    @Test
    public void test() {
        StudentDaoImpl studentDao = new StudentDaoImpl();
        Student stu = new Student();

        studentDao.add(stu);
        Student student = studentDao.get(0);

        System.out.println(student);

    }
}

如果Teacher有類似的操作,那么Dao這個接口就可以直接被復(fù)用:

public class TeacherDaoImpl implements Dao<Teacher> {
    List<Teacher> teacherList = new ArrayList<>();
    @Override
    public void add(Teacher teacher) {
        teacherList.add(teacher);
    }

    @Override
    public Teacher get(int index) {
        return teacherList.get(index);
    }
}

泛型方法

類本身可能是泛型的主守,也可能不是禀倔,和它的方法是否是泛型的沒有關(guān)系,泛型方法是獨(dú)立于類的参淫。如果方法是static的救湖,則無法訪問該類的泛型類型參數(shù),因此涎才,如果使用了泛型類型參數(shù)鞋既,則它必須是泛型方法。

要定義泛型方法耍铜,請將泛型類型參數(shù)放在返回值之前邑闺,如下所示:

public class GenericMethods {
    public <T> void f(T x) { // 泛型方法
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }
}

盡管可以同時對類及其方法進(jìn)行參數(shù)化,但這里未將GenericMethods類參數(shù)化棕兼。只有方法 f() 具有類型參數(shù)陡舅,該參數(shù)由方法返回類型之前的參數(shù)列表指示。

對于泛型類程储,必須在實(shí)例化該類時指定類型參數(shù)蹭沛。使用泛型方法時,通常不需要指定參數(shù)類型章鲤,因?yàn)榫幾g器會找出這些類型摊灭。 這稱為 類型參數(shù)推斷。因此败徊,對 f() 的調(diào)用看起來像普通的方法調(diào)用帚呼,并且 f() 看起來像被重載了無數(shù)次一樣。它甚至能接受GenericMethods類型的參數(shù)皱蹦。

如果使用基本類型調(diào)用 f() 煤杀,自動裝箱就開始起作用,自動將基本類型包裝在它們對應(yīng)的包裝類型中沪哺。

下面的這兩種都不是泛型方法:

// 這不是泛型方法沈自,泛型方法的返回值前一定要有<T>
public void add(T t);
// 這個也不是泛型方法
public T get(int index);

需要注意的點(diǎn)

  1. 泛型類可能有多個參數(shù),此時應(yīng)將多個參數(shù)一起放在尖括號內(nèi)辜妓。比如<E1,E2,E3>

  2. 泛型類的構(gòu)造器如下: public GenericClass(){}

    而下面是錯誤的: public GenericClass<E>{}

  3. 實(shí)例化后枯途,操作原來泛型位置的結(jié)構(gòu)必須與指定的泛型類型一致滤灯。

  4. 泛型不同的引用不能相互賦值纺座。

    盡管在編譯時 ArrayList<String>和ArrayList<Integer>是兩種類型,但是,在運(yùn)行時只有一個ArrayList被加載到JVM中渤滞。

  5. 泛型如果不指定识樱,將被擦除迂烁,泛型對應(yīng)的類型均按照Object處理恭陡,但不等價于Object

    建議:泛型要使用就都用坦报。要不用库说,就都不要用。

  6. 如果泛型結(jié)構(gòu)是一個接口或抽象類燎竖,則不可創(chuàng)建泛型類的對象璃弄。

  7. JDK 7.0,泛型的簡化操作: ArrayList<Fruit>first= new ArrayList<>();(類型推斷)

  8. 泛型的指定中不能使用基本數(shù)據(jù)類型构回,可以使用包裝類替換夏块。

  9. 在類/接口上聲明的泛型,在本類或本接口中即代表某種類型纤掸,可以作為非靜態(tài)屬性的類型脐供、非靜態(tài)方法的參數(shù)類型、非靜態(tài)方法的返回值類型借跪。但在靜態(tài)方法中不能使用類的泛型政己。

  10. 異常類不能是泛型的。

  11. 如果Person類是Student類的父類掏愁,那么List<Person>List<Student>不具備子父類的關(guān)系歇由,二者屬于并列關(guān)系。但是Person<String>Student<String>的父類

  12. 不能使用new E[]果港。但是可以:E[] elements= (E[])new Object[capacity];

    參考:ArrayList源碼中聲明:Object[] elementData沦泌,而非泛型參數(shù)類型數(shù)組。

  13. 父類有泛型辛掠,子類可以選擇保留泛型也可以選擇指定泛型類型:

    • 子類不保留父類的泛型:按需實(shí)現(xiàn)
      • 沒有類型---擦除
      • 具體類型
    • 子類保留父類的泛型:泛型子類
      • 全部保留
      • 部分保留
    • 結(jié)論:子類除了指定或保留父類的泛型谢谦,還可以增加自己的泛型

代碼示例:

class Father<T1, T2> {
}

/**
 * 定義泛型子類Son
 * 情況一:繼承泛型父類后不保留父類的泛型
 */
//1.沒有指明類型  擦除
class Son1<A, B> extends Father {//等價于class Son1 extends Father<Object,Odject>{}
}

//2.指定具體類型
class Son2<A, B> extends Father<Integer, String> {
}

/**
 * 定義泛型子類Son
 * 情況二:繼承泛型父類后保留泛型類型
 */
//1.全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}

//2.部分保留
class Son4<T2, A, B> extends Father<Integer,T2>{
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市萝衩,隨后出現(xiàn)的幾起案子回挽,更是在濱河造成了極大的恐慌,老刑警劉巖猩谊,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件千劈,死亡現(xiàn)場離奇詭異,居然都是意外死亡牌捷,警方通過查閱死者的電腦和手機(jī)墙牌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門袁梗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人憔古,你說我怎么就攤上這事×苄洌” “怎么了鸿市?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長即碗。 經(jīng)常有香客問我焰情,道長,這世上最難降的妖魔是什么剥懒? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任内舟,我火速辦了婚禮,結(jié)果婚禮上初橘,老公的妹妹穿的比我還像新娘验游。我一直安慰自己,他們只是感情好保檐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布耕蝉。 她就那樣靜靜地躺著,像睡著了一般夜只。 火紅的嫁衣襯著肌膚如雪垒在。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天扔亥,我揣著相機(jī)與錄音场躯,去河邊找鬼。 笑死旅挤,一個胖子當(dāng)著我的面吹牛踢关,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谦铃,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼耘成,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驹闰?” 一聲冷哼從身側(cè)響起瘪菌,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹朗,沒想到半個月后师妙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡屹培,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年默穴,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔檩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡蓄诽,死狀恐怖薛训,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仑氛,我是刑警寧澤乙埃,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站锯岖,受9級特大地震影響介袜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜出吹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一遇伞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捶牢,春花似錦鸠珠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竹勉,卻和暖如春飞盆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背次乓。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工吓歇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人票腰。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓城看,卻偏偏與公主長得像,于是被迫代替她去往敵國和親杏慰。 傳聞我的和親對象是個殘疾皇子测柠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容