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; } }
我們發現在類的開頭多了個<T>,這個就代表著傳入進來的參數,他可以是整型,可以是字符串類型,只要你傳進來了那么后續的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,結束!