Java泛型

1、為什么需要泛型?

1.1可以為多種數(shù)據(jù)類型執(zhí)行相同的代碼

public class NonGeneric {
    
    public int addInt(int x,int y){
        return x + y;
    }

    public float addFloat(float x,float y){
        return x + y;
    }

    public static void main(String[] args) {
        NonGeneric nonGeneric = new NonGeneric();
        System.out.println(nonGeneric.addInt(1,2));
        System.out.println(nonGeneric.addFloat(1f,2f));
    }
    
}

我們可以看到上面這段代碼搜立,int類型以躯,float類型都有各自的加法方法,如果我們還要增加double類型的話啄踊,我們還要再加一個double的加法方法忧设,這樣顯然是麻煩的。

1.2可以在編譯期間指定數(shù)據(jù)類型社痛,不需要強制類型轉換见转,以及插入錯誤的數(shù)據(jù)類型在編譯期間就能發(fā)現(xiàn)。

public class NonGeneric {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("String");
        list.add(100);
        int size = list.size();
        for (int i = 0; i < size; i++) {
            String name = (String) list.get(i);
            System.out.println("name:" + name);
        }
    }
}

運行后報錯
image.png

2蒜哀、泛型類斩箫、泛型接口和泛型方法

2.1泛型方法

完全獨立的吏砂,不一定非要定義在泛型類或泛型接口中,在普通類中同樣可以定義乘客。
在訪問修飾符和返回值之間聲明<T>狐血,這種方法就是泛型方法,這是必要的易核,否則不是泛型方法匈织。

public static <T> T genericMethod(T... a){
        return a[0];
    }

2.2泛型類和泛型接口的定義

//泛型類
public class NormalGeneric<T> {
    private T data; 
    
    //該方法不是泛型方法 只不過返回值是泛型T 只有加入<T>的方法才是泛型方法
    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
//泛型接口
public interface IGenertor<T> {
    T next();
}
//泛型接口實現(xiàn)類1 不指明確切類型
public class ImpGenertor<T> implements IGenertor<T> {
    @Override
    public T next() {
        return null;
    }
}
//泛型接口實現(xiàn)類2 明確具體類型
public class ImpGenertor2 implements IGenertor<String> {
    @Override
    public String next() {
        return null;
    }
}

泛型方法和泛型類或泛型接口的區(qū)別
泛型類或泛型接口在創(chuàng)建實例對象時傳入具體類型,泛型方法在調用時傳入(不傳入也可以牡直,編譯器會自動識別)

public class NonGeneric {

    public static <T> T genericMethod(T... a){
        return a[0];
    }

    public static void main(String[] args) {
        //泛型類
        ImpGenertor<String> impGenertor = new ImpGenertor();
        //泛型接口 自動識別類型
        System.out.println(genericMethod("111","2222"));
    }
}

泛型方法和泛型類或泛型接口 可以有多個泛型
例如:

public interface IGenertor<T,K> {
    T next();
    K firrs();
}

下面我們看一段代碼

public class NonGeneric {
    static class Fruit{
        @Override
        public String toString() {
            return "水果";
        }
    }

    static class Apple extends Fruit{
        @Override
        public String toString() {
            return "蘋果";
        }
    }

    static class Person{
        @Override
        public String toString() {
            return "人";
        }
    }

    static class GenericTest<T> {

        public void show_1(T t) {
            System.out.println(t.toString());
        }
        
        public <E> void show_2(E e) {
            System.out.println(e.toString());
        }

        public <T> void show_3(T t) {
            System.out.println(t.toString());
        }

    }

    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Apple apple = new Apple();
        Person person = new Person();

        GenericTest<Fruit> genericTest = new GenericTest<>();

        genericTest.show_1(fruit);
        genericTest.show_1(apple);
        //編譯器會報錯
//        genericTest.show_1(person);

        genericTest.show_2(apple);
        genericTest.show_2(fruit);
        genericTest.show_2(person);

        genericTest.show_3(apple);
        genericTest.show_3(fruit);
        genericTest.show_3(person);
    }
}

由上面的代碼可知:
1缀匕、不是聲明在泛型類里面的就是泛型方法
2、在泛型類中使用泛型方法碰逸,也要在訪問修飾符和返回值之間將泛型<T>聲明出來
3乡小、泛型方法聲明的泛型是獨立的,所以show_2可以傳入任意類型饵史,不和泛型類中定義的泛型有任何關系
4满钟、如果泛型方法聲明的泛型和泛型類中聲明的泛型名稱一致,也遵循上一條胳喷,所以show_3使用上與show_2沒有任何區(qū)別
5湃番、泛型類聲明的泛型只影響泛型類中使用該泛型的部分,不會影響獨立的泛型方法吭露。

3吠撮、如何限定類型變量

public static <T> T min(T a,T b){
        if (a.compareTo(b) > 0) return a; else return b;
    }

上面這段代碼編譯器會報錯,因為編譯器不能確定T類型一定含有compareTo方法奴饮,那么我們如何保證執(zhí)行泛型這個類型變量一定有某個方法或遵循某種規(guī)則呢纬向?
我們如果在傳入?yún)?shù)時,限制只有實現(xiàn)compareTo方法才可以傳入是不是就解決了這個問題戴卜,這個思想就是類型變量的限定逾条。
我們將代碼改為下面這種形式

public static <T extends Comparable> T min(T a,T b){
        if (a.compareTo(b) > 0) return a; else return b;
    }

這樣就沒問題了,限制T必須實現(xiàn)或者派生自Comparable這個接口
T表示應該綁定類型的子類型
Comparable表示綁定類型也就是限定類型
T和Comparable 可以是類 也可以是接口
限定類型可以是多個投剥,用&連接师脂,但是要注意,如果限定類型有接口江锨,也有類吃警,類必須要寫在限定類型的第一個,而且必須是有且僅有一個類啄育。

//這么寫會報錯
public static <T extends Comparable& ArrayList> T min(T a, T b){
        if (a.compareTo(b) > 0) return a; else return b;
    }
//這么寫沒問題
  public static <T extends ArrayList&Comparable> T min(T a, T b){
        if (a.compareTo(b) > 0) return a; else return b;
    }

為什么只能有一個類?
因為Java是單繼承的
為什么一定要寫在第一個?
問Java去吧 我也不知道
注意:上面寫的示例是泛型方法酌心,但是規(guī)則對泛型類和泛型接口同樣適用。

4挑豌、泛型使用中的約束和局限性

1安券、不能實例化類型變量
2墩崩、靜態(tài)域或者靜態(tài)方法里不能使用,需要注意的是靜態(tài)方法本身是泛型方法就可以使用
3侯勉、基本數(shù)據(jù)類型不可以鹦筹,可以傳入基礎類型的包裝類,為什么不可以,因為T只能是對象址貌,基礎數(shù)據(jù)類型不是對象
4铐拐、不能使用instanceof關鍵字 判斷具體類型
5、泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
6练对、可以定義泛型數(shù)組 但是不能初始化
7遍蟋、泛型類不能extends Exception/Throwble
8、不能捕獲泛型類對象

public class Restrict<T> {
    private T data;

    //1锹淌、不能實例化類型變量
//    public Restrict() {
//        this.data = new T();
//    }

    //2匿值、靜態(tài)域或者靜態(tài)方法里不能使用
//    private static T instance;
    //靜態(tài)方法本身是泛型方法就可以使用
//    private static <T> T getInstance(){
//        T a = null;
//        return a;
//    };

    public static void main(String[] args) {
        //3赠制、基本數(shù)據(jù)類型不可以會報錯
//        Restrict<double>

        Restrict<Double> restrict = new Restrict<>();
        //4赂摆、不能使用instanceof關鍵字 判斷具體類型 這里會報錯
//        if(restrict instanceof Restrict<Double>)

        //5、泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
        Restrict<String> restrictStr = new Restrict<>();
        //這里打印結果為true
        System.out.println(restrict.getClass() == restrictStr.getClass());
        //下面打印的名字是一樣的  Restrict類名  泛型在使用的時候 關于這個類的類型不管你傳入的是什么類型參數(shù) 打印出來的類名都是一樣的
        System.out.println(restrict.getClass().getName());
        System.out.println(restrictStr.getClass().getName());

        //打印結果
//        true
//        com.hot.lib.day1.Restrict
//        com.hot.lib.day1.Restrict

        //6可以定義泛型數(shù)組 但是不能初始化
        Restrict<Double>[] restrictArray;
        Restrict<Double>[] restricts = new Restrict<Double>[10];

    }

    //7钟些、泛型類不能extends Exception/Throwble 這里編譯器會報錯
//    private class Problem<T> extends Exception{}

    //8烟号、不能捕獲泛型類對象
//    public <T extends Throwable> void dowork(T x){
//        try {
//
//        }catch (T t){
//
//        }
//    }
    
    //這種方式可以
    public <T extends Throwable> void dowork(T x) throws T{
        try {

        }catch (Throwable t){
            throw x;
        }
    }
}

5、泛型類型的繼承規(guī)則

這里我們用代碼來提現(xiàn)

class Employee {
}

class Worker extends Employee {
}

public class Pair<T> {

    public static void main(String[] args) {
        Pair<Worker> workerPair = new Pair<>();
        Pair<Employee> employeePair = new Pair<>();

        //我們平時這么寫是沒問題的  因為Worker extends Employee
        Employee employee = new Worker();

        //1政恍、這里編譯器會報錯  workerPair和employeePair 不存在繼承關系
//        Pair<Employee> employeePair1 = new Pair<Worker>();

        //這里是沒問題的
          Pair<Employee> pair = new ExtendPair<>();
        
    }

    //2汪拥、泛型類可以繼承或者擴展其他泛型類 比如List和ArrayList
    private static class ExtendPair<T> extends Pair<T>{}
}

6、泛型中的通配符類型

只能用在方法中篙耗,類中是不可以使用的
這里比較復雜迫筑,首先我們先看幾個測試類。

public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

public class Food {
}

public class Fruit extends Food{
}

public class Apple extends Fruit{
}

public class Orange extends Fruit{
}

public class Tomatoes extends Apple {
}

注意上面這些類的繼承關系宗弯。

我們首先看第一種情況

 public static void println(GenericType<Fruit> p){
        System.out.println(p.getData());
    }

    public static void use(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        println(fruitGenericType);
        //編譯器報錯
//        println(appleGenericType);
    }

這種情況很簡單脯燃,我們上面說過,泛型參數(shù)之間存在繼承關系蒙保,但是泛型類沒有任何關系辕棚,所以這里傳入GenericType<Apple>類型會報錯。正是因為如此邓厕,才引入了通配符的概念逝嚎。

接下來我們看下面這段代碼

public static void println(GenericType<? extends Fruit> p){
        System.out.println(p.getData());
    }

    public static void use(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<Food> foodGenericType = new GenericType<>();
        println(fruitGenericType);
        //這時可以編譯通過
        println(appleGenericType);
        //編譯報錯
//        println(foodGenericType);

        GenericType<? extends Fruit> genericType = new GenericType<>();
        //不會報錯
        Fruit fruit = genericType.getData();
        //下面兩行代碼都會報錯
//        genericType.setData(new Apple());
//        genericType.setData(new Fruit());
    }

這里我們可以看到打印方法的泛型改為? extends Fruit,详恼?就是通配符补君,extends則是我們上面講的限定符(上界限定符),這里的意思是昧互,限定泛型類型為Fruit的派生類挽铁,包括Fruit本身她紫。所以我們看到,println傳入Fruit的子類型都沒問題了屿储,但是傳入Food就會報錯贿讹,因為Food是Fruit的父類。

接下來我們看到够掠,我們創(chuàng)建了一個genericType對象民褂,然后分別調用了getData和setData方法。
getData方法返回的是Fruit類型疯潭,因為我們知道傳入的上限就是Fruit類型赊堪,如果傳入的是Fruit派生類型,也是可以轉為Fruit類型竖哩,所以這里會返回Fruit類型哭廉。

然后我們發(fā)現(xiàn),setData方法會報錯相叁,因為我們沒有指定確切的類型遵绰,雖然我們傳入的是Fruit的子類,但是編譯器不知道是哪個子類增淹,所以會報錯椿访。

extends限定符的作用就是可以安全的訪問數(shù)據(jù),也就是extends后面的類型數(shù)據(jù)虑润,即例子中的Fruit類型數(shù)據(jù)成玫。

接下來我們看另外一種情況

public static void println(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void use(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        GenericType<Tomatoes> tomatoesGenericType = new GenericType<>();
        println(fruitGenericType);
        println(appleGenericType);
        //下面兩行編譯報錯
//        println(orangeGenericType);
//        println(Tomatoes);

        GenericType<? super Apple> genericType = new GenericType<>();
        //不會報錯
        Object fruit = genericType.getData();
        //不會報錯
        genericType.setData(new Apple());
        //編譯報錯
//        genericType.setData(new Fruit());
    }

這種情況與上面的區(qū)別就是將extends換成了super,super是下界限定符拳喻,即只能是Apple的父類及其自身哭当。所以我們看到只能傳入父類型,傳入子類型或平級的類型會報錯冗澈。

這時調用getData方法钦勘,會返回Object類型,不會返回具體類型渗柿,這很好理解个盆,因為我們限制了只能傳入Apple的父類型,但是我們并不知道具體是哪個父類型朵栖,因為Object是所有類的基類颊亮,所以這里返回了Object。

然后我們看setData調用陨溅,這里的邏輯可能會有些讓人不舒服终惑,我們可以看到傳入Apple的父類型,也就是Fruit類型會報錯门扇,大家一定會疑惑這是為什么雹有,我們明明限定的是只能是Apple的父類型啊偿渡,為什么傳入父類型還會報錯呢?
我們可以換個角度想一想溜宽,我們傳入一個對象進來,可定要調用它的某個方法质帅,但是我們如果傳入的是父類适揉,可能會沒有子類的方法剪侮,但是子類一定擁有父類的方法,這樣可能邏輯上就說的通了洛退,這里是我自己的理解瓣俯,如有不對大家請指出。

通過上面的代碼我們可以發(fā)現(xiàn)不狮,extends是為了安全的訪問數(shù)據(jù)降铸,而super是為了安全的寫入數(shù)據(jù)。

7摇零、虛擬機是如何實現(xiàn)泛型的

java實現(xiàn)泛型使用了類型擦除

public class GenericType<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

這段代碼在虛擬機中會變成下面這樣

public class GenericType<Object> {
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

也就是泛型T變成了Object,這也就是所謂的泛型擦除
如果是下面這種狀況

public class GenericType<T extends ArrayList & Comparable> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

如果是上面這種情況桶蝎,T則會變成ArrayList驻仅。如果使用了限定符,則會轉為限定符后面的第一個類型登渣。
如果調用了Comparable相關的方法噪服,則會在變量前面進行強轉,即下面這種情況胜茧。

public void setData(T data) {
        this.data = data;
//        (Comparable)data.compareTo()
    }

接下來我們看下面這段代碼

    public static void ListMethod(List<String> list){
        
    }

    public static void ListMethod(List<Integer> list){

    }

我們知道方法的重載是參數(shù)列表不同粘优,但是上面的代碼編譯器是會報錯的,也是因為泛型擦除呻顽,即List參數(shù)類型都變?yōu)榱薒ist<Object>了雹顺。

最后我們看下泛型是怎么生效的

public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        map.put("test","aaaa");
        System.out.println(map.get("test"));
    }
//編譯為class之后樣式
public static void main(String[] args) {
        Map<String, String> map = new HashMap();
        map.put("test", "aaaa");
        System.out.println((String)map.get("test"));
    }

我們發(fā)現(xiàn)在字節(jié)碼中對map.get("test")進行了強轉,這也就是泛型的實現(xiàn)方式廊遍,強轉。

泛型擦除并不是真正意義上的擦沒了,泛型的類型會保存在字節(jié)碼文件中阳柔,這樣才實際調用的時候回進行強轉。

以上就是泛型相關的一些知識了王财,如果有哪些不對的地方,希望大家指出裕便,共同進步绒净。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偿衰,隨后出現(xiàn)的幾起案子疯溺,更是在濱河造成了極大的恐慌,老刑警劉巖哎垦,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囱嫩,死亡現(xiàn)場離奇詭異,居然都是意外死亡漏设,警方通過查閱死者的電腦和手機墨闲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來郑口,“玉大人鸳碧,你說我怎么就攤上這事∪裕” “怎么了瞻离?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乒裆。 經常有香客問我套利,道長,這世上最難降的妖魔是什么鹤耍? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任肉迫,我火速辦了婚禮,結果婚禮上稿黄,老公的妹妹穿的比我還像新娘喊衫。我一直安慰自己,他們只是感情好杆怕,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布族购。 她就那樣靜靜地躺著,像睡著了一般陵珍。 火紅的嫁衣襯著肌膚如雪寝杖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天撑教,我揣著相機與錄音朝墩,去河邊找鬼。 笑死,一個胖子當著我的面吹牛收苏,可吹牛的內容都是我干的亿卤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼鹿霸,長吁一口氣:“原來是場噩夢啊……” “哼排吴!你這毒婦竟也來了?” 一聲冷哼從身側響起懦鼠,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤钻哩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肛冶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體街氢,經...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年睦袖,在試婚紗的時候發(fā)現(xiàn)自己被綠了珊肃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡馅笙,死狀恐怖伦乔,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情董习,我是刑警寧澤烈和,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站皿淋,受9級特大地震影響招刹,放射性物質發(fā)生泄漏。R本人自食惡果不足惜沥匈,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一蔗喂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧高帖,春花似錦、人聲如沸畦粮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宣赔。三九已至预麸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間儒将,已是汗流浹背吏祸。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留钩蚊,地道東北人贡翘。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓蹈矮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸣驱。 傳聞我的和親對象是個殘疾皇子泛鸟,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348