Java泛型理解與使用

1.泛型簡介

  • 問題:在獲取用戶信息的API中,後臺給咱們返回一個這樣形式的json字符串。java

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

    咱們用fastJson解析上述json字符串時候,該怎麼處理? ,咱們是否是就會寫這樣一個類。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返回的數據格式是這樣的一個形式,咱們該怎麼處理?bash

    {
        "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"
            }
        ]
    }
    複製代碼

    是否是咱們仍然要再寫一個解析類來解析這個設備列表類。ide

    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;
            }
        }
    }
    
    複製代碼

    若是每次都這樣的話,會不會要建立不少很相像的類,他們只是裏面部分變量不一樣,其餘的部分都相同。ui

    再舉一個栗子: 若是咱們想要產生多個對象,每一個對象的邏輯徹底同樣,只是對象內的成員變量的類型不一樣,那咱們如何去作? 在下面咱們建立了兩個類,只是data的變量類型不一樣,是否是也能夠達到咱們剛纔的要求。this

    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;
        }
    }
    複製代碼

    打印結果:spa

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

    輸出:code

    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,結束!

相關文章
相關標籤/搜索