Java泛型理解與使用

正兒八經小青年就是我發表於2019-02-19

1.泛型簡介

  • 問題:在獲取使用者資訊的API中,後臺給我們返回一個這樣形式的json字串。

    {
        "meta": {
            "code": 0,
            "message": "ok"
        },
        "data": {
            "nick_name": "hellokitty",
            "cellphone": "18301824843",
        }
    }
    複製程式碼

    我們用fastJson解析上述json字串時候,該怎麼處理?
    ,我們是不是就會寫這樣一個類。

    public class User {
    
    
        private Meta meta;
        private Data data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public Data getData() {
            return data;
        }
    
        public void setData(Data data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    
        static class Data
        {
    
            private String nick_name;
            private String cellphone;
    
            public String getNick_name() {
                return nick_name;
            }
    
            public void setNick_name(String nick_name) {
                this.nick_name = nick_name;
            }
    
            public String getCellphone() {
                return cellphone;
            }
    
            public void setCellphone(String cellphone) {
                this.cellphone = cellphone;
            }
        }
    }
    複製程式碼

    然後呼叫fastjason的JSON.parseObject(msg,User.class)進行解析。

    而如果拉取裝置列表API返回的資料格式是這樣的一個形式,我們該怎麼處理?

    {
        "meta": {
            "code": 0,
            "message": "ok"
        },
        "data": [
            {
                "device_id": "4acb634aaf5711e8b290000c29c27f42",
                "role": 1,
                "device_alias": "hellokitty",
                "created_at": "2018-09-04T10:55:57"
            },
            {
                "device_id": "4acb634aaf5711e8b290000c29c27f42",
                "role": 1,
                "device_alias": "hellokitty",
                "created_at": "2018-09-04T10:55:57"
            }
        ]
    }
    複製程式碼

    是不是我們仍然要再寫一個解析類來解析這個裝置列表類。

        public class DeviceList {
    
        private Meta meta;
        private List<Device> data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public List<Device> getData() {
            return data;
        }
    
        public void setData(List<Device> data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    
        static class Device
        {
            @Override
            public String toString() {
                return "Device{" +
                        "device_id=`" + device_id + ``` +
                        ", role=" + role +
                        ", device_alias=`" + device_alias + ``` +
                        ", created_at=`" + created_at + ``` +
                        `}`;
            }
    
            private String device_id;
            private int role;
            private String device_alias;
            private String created_at;
    
            public String getDevice_id() {
                return device_id;
            }
    
            public void setDevice_id(String device_id) {
                this.device_id = device_id;
            }
    
            public int getRole() {
                return role;
            }
    
            public void setRole(int role) {
                this.role = role;
            }
    
            public String getDevice_alias() {
                return device_alias;
            }
    
            public void setDevice_alias(String device_alias) {
                this.device_alias = device_alias;
            }
    
            public String getCreated_at() {
                return created_at;
            }
    
            public void setCreated_at(String created_at) {
                this.created_at = created_at;
            }
        }
    }
    
    複製程式碼

    如果每次都這樣的話,會不會要建立很多很相像的類,他們只是裡面部分變數不同,其他的部分都相同。

    再舉一個栗子:
    如果我們想要產生多個物件,每個物件的邏輯完全一樣,只是物件內的成員變數的型別不同,那我們如何去做?
    在下面我們建立了兩個類,只是data的變數型別不同,是不是也可以達到我們剛才的要求。

     static class MyClass1
    {
        public MyClass1() {
        }
    
        private String data;
    
        public MyClass1(String data) {
            this.data = data;
        }
    
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            this.data = data;
        }
    }
    
    static class MyClass2
    {
        public MyClass2() {
        }
    
        private int data;
    
        public MyClass2(int data) {
            this.data = data;
        }
    
        public int getData() {
            return data;
        }
    
        public void setData(int data) {
            this.data = data;
        }
    }
    複製程式碼

    列印結果:

        MyClass1 myClass1 = new MyClass1();
        myClass1.setData("Cyy");
        MyClass2 myClass2 = new MyClass2();
        myClass2.setData(10);
        System.out.println(myClass1.getData());
        System.out.println(myClass2.getData());
    複製程式碼

    輸出:

    Cyy
    10
    複製程式碼

    但是如果我們還想要這樣一個物件呢,那我們是不是還要繼續去建立這樣的物件,那如果我還要10個這個的物件呢,那我們是不是就要建立十個。這樣明顯是很笨重的一種解決方案。

    那我們現在思考,如果我們用Object來代替呢?

        static class MyClass1
        {
            public MyClass1() {
            }
    
            private Object data;
    
            public MyClass1(Object data) {
                this.data = data;
            }
    
            public Object getData() {
                return data;
            }
    
            public void setData(Object data) {
                this.data = data;
            }
        }
    複製程式碼

    列印輸出:

       MyClass1 myClass1 = new MyClass1();
        myClass1.setData("Cyy");
        System.out.println((String)myClass1.getData());
        MyClass1 myClass2 = new MyClass1();
        myClass2.setData(10);
        System.out.println((int)myClass2.getData());
    
        ```
    輸出結果:
    
    複製程式碼
      Cyy
      10
    複製程式碼
    呀~看上去好像完美解決了,不用建立多個類,就可以實現剛才需要功能,好像很完美,現在讓他變成不完美,現在我們讓他這樣列印出來.
    複製程式碼
       MyClass1 myClass2 = new MyClass1();
          myClass2.setData(10);
          System.out.println((String)myClass2.getData());
    複製程式碼
    注意我們給他的是整型,但是列印時候我們給他的強轉型別是String,現在看下會發生什麼問題。
    複製程式碼
      Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
      at SecDemo.main(SecDemo.java:13)
    複製程式碼
    它提示了,型別轉換異常。
    
     總結
            
        方案(一) :
            
        方法: 建立多個類檔案,給每個類中的成員變數設定指定的資料型別。
            
        缺點: 導致類的膨脹,重用性太差
            
        方案(二) :
            
        方法: 建立一個類檔案,給這個類中的成員變數設定Object資料型別
            
        缺點:編譯的時候正常,但執行時候可能會報錯.
            
        泛型類就能很好的解決以上兩個問題。
    
    複製程式碼

2.泛型類

  • 泛型是JDK1.5引入的新特性,也是最重要的一個特性。

  • 泛型可以在編譯的時候檢查型別安全,並且所有的強制轉換都是自動和隱式的。

  • 泛型的原理就是型別的引數化,即把型別看做引數,也就是說把所要操作的資料型別看做引數,就像方法的形式引數是執行時傳遞的值一樣。

  • 簡單的說,型別變數扮演的角色如同一個引數,它提供給編譯器用來型別檢查的資訊。

  • 泛型可以提高程式碼的擴充套件性和重用性
    **

    如果我們將剛才的類改成泛型類是什麼樣子的呢?

    static class MyClass1<T>
    {
        public MyClass1() {
        }
    
        private T data;
    
        public MyClass1(T data) {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    複製程式碼

    我們發現在類的開頭多了個,這個就代表著傳入進來的引數,他可以是整型,可以是字串型別,只要你傳進來了那麼後續的get,set方法就全部都是這種型別了。他就相當於一個操作的引數。好的現在我們試一下。

    列印輸出:

    MyClass1 myClass1 = new MyClass1<String>();
        myClass1.setData("Cyy");
        System.out.println(myClass1.getData());
        MyClass1 myClass2 = new MyClass1<Integer>();
        myClass2.setData(10);
        System.out.println(myClass2.getData());
    複製程式碼

    輸出:

    Cyy
    10
    
    複製程式碼

    有沒有發現,我們不用進行強制型別轉換仍然能輸出正確的數值。
    注意下,當我們new MyClass1<String>()傳的是String那麼我們類裡面的所有T就都是String型別了。

    總結:

    泛型類使用優點:

    • 防止類膨脹

    • 不再手動進行型別轉換

    泛型類的使用
    1. 泛型的型別引數可以是泛型類
    static class MyClass1<T1>
        {
            public MyClass1() {
            }
    
            private T1 data1;
    
            public T1 getData1() {
                return data1;
            }
    
            public void setData1(T1 data1) {
                this.data1 = data1;
            }
        }
    
        static class Student
        {
            private String name;
    
            public Student(String name) {
                this.name = name;
            }
    
            @Override
            public String toString() {
                return "Student{" +
                        "name=`" + name + ``` +
                        `}`;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
        }
        
    複製程式碼

    使用:

     MyClass1<MyClass1<Student>> myClass1MyClass1 = new  MyClass1<MyClass1<Student>>();
            MyClass1<Student> myClass1 = new MyClass1<Student>();
            myClass1.setData1(new Student("cyy"));
            myClass1MyClass1.setData1(myClass1);
            System.out.println(myClass1MyClass1.getData1().getData1().toString());
    複製程式碼

    輸出:

    Student{name=`cyy`}
    複製程式碼
    1. 泛型類可以同時設定多個型別引數
    static class MyClass1<T1,T2>
        {
            public MyClass1() {
            }
    
            private T1 data1;
            private T2 data2;
    
            public T2 getData2() {
                return data2;
            }
    
            public void setData2(T2 data2) {
                this.data2 = data2;
            }
    
            public T1 getData1() {
                return data1;
            }
    
            public void setData1(T1 data1) {
                this.data1 = data1;
            }
        }
    複製程式碼

    使用:

    MyClass1<String,Integer> myClass1 = new MyClass1<String,Integer>();
            myClass1.setData1("Cyy");
            myClass1.setData2(25);
            System.out.println(myClass1.getData1());
            System.out.println(myClass1.getData2());
    複製程式碼

    輸出:

    Cyy
    25
    複製程式碼
    1. 泛型類可以繼承泛型類
    class SuperClass<T1>
        {
            private T1 var1;
    
            public SuperClass(T1 var1) {
                this.var1 = var1;
            }
    
            public T1 show1()
            {
                return var1;
            }
        }
    
        class SubClass<T1,T2> extends SuperClass<T1>
        {
            private T2 var2;
    
            public SubClass(T1 var1, T2 var2) {
                super(var1);
                this.var2 = var2;
            }
    
            @Override
            public T1 show1() {
                return super.show1();
            }
        }
    複製程式碼

    使用:

        SubClass<String,Integer> subClass = new SubClass<>("cyy",25);
        System.out.println(subClass.show1());
    複製程式碼

    輸出:

    cyy
    
    複製程式碼
    1. 泛型類可以實現泛型介面
     interface IInfo<T2>
        {
            public void show2(T2 var3);
        }
      static class SubClass<T1,T2> extends SuperClass<T1> implements IInfo<T2>
        {
            private T2 var2;
    
            public SubClass(T1 var1, T2 var2) {
                super(var1);
                this.var2 = var2;
            }
    
            @Override
            public T1 show1() {
                return super.show1();
            }
    
            @Override
            public void show2(T2 var3) {
    
                System.out.println(var3);
                System.out.println(var2);
            }
        }
    複製程式碼

    使用:

     SubClass<String,Integer> subClass = new SubClass<>("cyy",25);
            subClass.show2(100);
            System.out.println(subClass.show1());
    複製程式碼

    輸出:

    100
    25
    cyy
    複製程式碼

    注:不可以進行泛型變數之間的運算,因為泛型變數在編譯期間會進行型別擦除,全部變成Object,比如Object+Object就不知道是什麼型別了,所以這點很重要。

    OK,現在我們可以回到最初那個問題上了,我們可以利用泛型定義一個CommResult類。

    public class CommResult <T> {
    
        private Meta meta;
        private T data;
    
        public Meta getMeta() {
            return meta;
        }
    
        public void setMeta(Meta meta) {
            this.meta = meta;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        static class Meta
        {
            private String code;
            private String message;
    
            public String getCode() {
                return code;
            }
    
            public void setCode(String code) {
                this.code = code;
            }
    
            public String getMessage() {
                return message;
            }
    
            public void setMessage(String message) {
                this.message = message;
            }
        }
    }
    複製程式碼

    然後使用的時候我們可以這樣:

    JSON.parseObject(msg,CommResult<User>)JSON.parseObject(msg,CommResult<List<Device>>)

    這樣就完美避免了建立多個結構一樣,但是隻有裡面部分變數不一致的類了。

    3.限制泛型可用型別

    在定義泛型類別時,預設在例項化泛型類的時候可以使用任何型別,但是如果想要限制使用泛型時,只能用某個特定型別或者是其子型別才能例項化該型別時,可以在定義型別時,使用extends關鍵字指定這個型別必須是繼承某個類,或者實現某個介面。
    當沒有指定泛型繼承的型別或介面時,預設使用extends Object,所以預設情況下,可以使用任何型別作為引數。

     class GenericClass<T extends Animal>
        {
            private T data;
    
            public GenericClass(T data)
            {
                this.data = data;
            }
    
            public T getData() {
                return data;
            }
    
            public void setData(T data) {
                this.data = data;
            }
        }
    
        abstract class Animal
        {
            public abstract void eat();
        }
    
        class Dog extends Animal
        {
            @Override
            public void eat() {
    
                System.out.println("啃骨頭");
            }
        }
    
        class Cat extends Animal
        {
    
            @Override
            public void eat() {
    
                System.out.println("吃魚肉");
            }
        }
    複製程式碼

    現在我們看下,如果我在泛型類裡面傳個String型別的引數,看他會報什麼?
    Type parameter `java.lang.String` is not within its bound; should extend `Test.Animal

    他說String不是Animal子類,不行吧。

    如果我們換成這樣就可以了。

    GenericClass<Dog> genericClass = new GenericClass<>(new Dog());
            genericClass.getData().eat();
            GenericClass<Cat> genericClasscat = new GenericClass<>(new Cat());
            genericClasscat.getData().eat();
    複製程式碼

    如果換成介面呢?

    class GenericClass<T implements eat>
    {
        private T data;
    
        public GenericClass(T data)
        {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    複製程式碼

    這樣寫對不對,這樣寫是不對的,編譯器會報錯的,因為不管是介面還是類,都要用extends。所以換成介面也要寫成這樣就可以了。

    class Cat implements eat
    {
    
        @Override
        public void eat() {
    
            System.out.println("吃魚肉");
        }
    }
    
    class Dog implements eat
    {
    
        @Override
        public void eat() {
    
            System.out.println("啃骨頭");
        }
    }
    
    interface eat
    {
        public abstract void eat();
    }
    
    class GenericClass<T extends eat>
    {
        private T data;
    
        public GenericClass(T data)
        {
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    複製程式碼

    4.型別通配宣告

    同一泛型類,如果例項化時給定的實際型別不同,則這些例項的型別是不相容的,不能相互賦值。如:

    Generic<Boolean> f1 = new Generic<Booleab>();
    Generic<integer> f2 = new Generic<integer>();
    f1 = f2;//發生編譯錯誤
    Generic<Object> f = f1 ;//f1和f型別並不相容,發生編譯錯誤
    f = f2;//f2和f型別同樣不相容,也會發生編譯錯誤。
    複製程式碼

    泛型類例項之間的不相容性會帶來使用的不便。我們可以使用泛型萬用字元(?)生命泛型類的變數就可以解決這個問題。

    泛型通配的使用方式

    • “?” 代表一個型別。
    Generic<Boolean> f1 = new Generic<Booleab>();
    
    Generic<?> f= f1;
    
    複製程式碼
    • 和限制泛型的上線相似,同樣可以使用extends關鍵字限定萬用字元匹配型別的上線:
    Generic<Dog> f1 = new Generic<Dog>();
    
    Generic<? extends Animal> f= f1;
    複製程式碼
    • 還可以使用super關鍵詞將萬用字元匹配型別限定為某個型別及其父型別
    Generic<Animal> f1 = new Generic<Animal>();
    
    Generic<? super Dog> f= f1;
    複製程式碼

    現在要在這裡特別說下兩個限定萬用字元

    • extends 上邊界限定萬用字元
    舉個例子一看就懂了,<? extends Animal> , 那這裡的`?`就必須是Animal的子類或它自己。
    複製程式碼
    • super 下邊界限定萬用字元
    舉個例子一看就懂了,<? super Dog> , 那這裡的`?`就必須是Dog的父類或它自己。
    複製程式碼

    5.泛型方法使用

    不僅類可以宣告泛型,類中的方法也可以宣告僅用於自身的泛型,這種方法叫做泛型方法。其定義格式為:

    訪問修飾符<泛型列表> 返回型別 方法名(引數列表) 
    {
        實現程式碼
    }
    複製程式碼

    在泛型列表中宣告的泛型,可用於該方法的返回型別宣告,引數型別宣告和方法程式碼中的區域性變數的型別宣告。

    類中其他方法不能使用當前方法宣告的泛型。

    注:是否擁有泛型方法,與其所在的類是否是泛型沒有關係。要定義泛型方法,秩序將泛型引數列表置於返回值之前。

    什麼時候使用泛型方法,而不是泛型類呢?

    • 新增型別約束只作用於一個方法的多個引數之間,而不涉及類中的其他方法時。

    • 施加型別約束的方法為靜態方法,只能將其定義為泛型方法,因為靜態方法不能使用其所在類的型別引數。

    再舉個程式碼的例子:

    現在我們先定義一個泛型類:

    public class Demo1 {
    
       public static void main(String[] args)
       {
    
            GenericClassOne<String> genericClassOne = new GenericClassOne<>();
            genericClassOne.printlinT(10);
        }
    
    }
    
    
    class GenericClassOne<T>
    {
        public void printlinT(T content)
        {
            System.out.println(content);
        }
    }
    複製程式碼

    如果我們這麼寫,肯定編譯就報錯誤了吧,因為我們上面定義的是String型別,但是我們傳給他的是int型的。那如果這樣的話,這個方法是不是就有侷限性了。

    那如果我們現在使用泛型方法呢?該怎麼寫?

        public class Demo1 {
    
        public static void main(String[] args)
        {
    
            GenericClassOne genericClassOne = new GenericClassOne();
            genericClassOne.printlinT(10);
            genericClassOne.printlinT("cyy");
            genericClassOne.printlinT(12.5);
        }
    
    }
    
    
    class GenericClassOne<T>
    {
        //泛型方法,型別定義寫在返回值之前了
        public <T> void printlinT(T content)
        {
            System.out.println(content);
        }
    }
    複製程式碼

    這下不會再報編譯錯誤了,現在看下列印結果。

    輸出:

    10
    cyy
    12.5
    複製程式碼

    這樣是不是就靈活了許多啦~

    那麼泛型的方法可不可以過載呀,當然可以,我們仍然可以寫成這樣。

    class GenericClassOne<T>
    {
        //泛型方法,型別定義寫在返回值之前了
        public <T> void printlinT(T content)
        {
            System.out.println(content);
        }
    
        //泛型方法,型別定義寫在返回值之前了
        public <T extends Animal> void printlinT(T animal)
        {
            animal.eat();
        }
    }
    
    abstract class Animal
    {
        public abstract void eat();
    }
    複製程式碼

    因為泛型類在編譯過程中會有個擦除的工作,所以第一個printlnT(T content)中的泛型會變成object,而第二個泛型方法中的T會變成Animal。所以他的方法可以被過載。

    Ok,結束!

相關文章