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
型別了。總結:
泛型類使用優點:
-
防止類膨脹
-
不再手動進行型別轉換
泛型類的使用
- 泛型的型別引數可以是泛型類
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`} 複製程式碼
- 泛型類可以同時設定多個型別引數
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 複製程式碼
- 泛型類可以繼承泛型類
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 複製程式碼
- 泛型類可以實現泛型介面
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,結束!
-