1. 泛型概述
泛型(Generic type 或者 generics)是對 Java 語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。可以把類型參數看作是使用參數化類型時指定的類型的一個占位符,就像方法的形式參數是運行時傳遞的值的占位符一樣。
泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。 Java語言引入泛型的好處是安全簡單。
在Java SE 1.5之前,沒有泛型的情況的下,通過對類型Object的引用來實現參數的“任意化”,“任意化”帶來的缺點是要做顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型可以預知的情況下進行的。對于強制類型轉換錯誤的情況,編譯器可能不提示錯誤,在運行的時候才出現異常,這是一個安全隱患。
泛型的好處是在編譯的時候檢查類型安全,并且所有的強制轉換都是自動和隱式的,以提高代碼的重用率。
可以在集合框架(Collection framework)中看到泛型的動機。例如,Map 類允許您向一個 Map添加任意類的對象,即使最常見的情況是在給定映射(map)中保存某個特定類型(比如 String)的對象。
因為 Map.get() 被定義為返回 Object,所以一般必須將 Map.get() 的結果強制類型轉換為期望的類型,如下面的代碼所示:
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");
要讓程序通過編譯,必須將 get() 的結果強制類型轉換為 String,并且希望結果真的是一個 String。但是有可能某人已經在該映射中保存了不是 String 的東西,這樣的話,上面的代碼將會拋出 ClassCastException。
理想情況下,您可能會得出這樣一個觀點,即 m 是一個 Map,它將 String 鍵映射到 String 值。這可以讓您消除代碼中的強制類型轉換,同時獲得一個附加的類型檢查層,該檢查層可以防止有人將錯誤類型的鍵或值保存在集合中。這就是泛型所做的工作。
package cn.itcast_01;
import java.util.ArrayList;
import java.util.Iterator;
/*
* ArrayList存儲字符串并遍歷
*
* 我們按照正常的寫法來寫這個程序, 結果確出錯了。
* 為什么呢?
* 因為我們開始存儲的時候,存儲了String和Integer兩種類型的數據。
* 而在遍歷的時候,我們把它們都當作String類型處理的,做了轉換,所以就報錯了。
* 但是呢,它在編譯期間卻沒有告訴我們。
* 所以,我就覺得這個設計的不好。
* 回想一下,我們的數組
* String[] strArray = new String[3];
* strArray[0] = "hello";
* strArray[1] = "world";
* strArray[2] = 10;
* 集合也模仿著數組的這種做法,在創建對象的時候明確元素的數據類型。這樣就不會在有問題了。
* 而這種技術被稱為:泛型。
*
* 泛型:是一種把類型明確的工作推遲到創建對象或者調用方法的時候才去明確的特殊的類型。參數化類型,把類型當作參數一樣的傳遞。
* 格式:
* <數據類型>
* 此處的數據類型只能是引用類型。
* 好處:
* A:把運行時期的問題提前到了編譯期間
* B:避免了強制類型轉換
* C:優化了程序設計,解決了黃色警告線
*/
public class GenericDemo {
public static void main(String[] args) {
// 創建
ArrayList<String> array = new ArrayList<String>();
// 添加元素
array.add("hello");
array.add("world");
array.add("java");
// array.add(new Integer(100));
//array.add(10); // JDK5以后的自動裝箱
// 等價于:array.add(Integer.valueOf(10));
// 遍歷
Iterator<String> it = array.iterator();
while (it.hasNext()) {
// ClassCastException
// String s = (String) it.next();
String s = it.next();
System.out.println(s);
}
// 看下面這個代碼
// String[] strArray = new String[3];
// strArray[0] = "hello";
// strArray[1] = "world";
// strArray[2] = 10;
}
}
2. 泛型的好處
Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成為泛型化的了。這帶來了很多好處:
2.1 類型安全
泛型的主要目標是提高 Java 程序的類型安全。通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在于程序員的頭腦中(或者如果幸運的話,還存在于代碼注釋中)。
Java 程序中的一種流行技術是定義這樣的集合,即它的元素或鍵是公共類型的,比如“String 列表”或者“String 到 String 的映射”。通過在變量聲明中捕獲這一附加的類型信息,泛型允許編譯器實施這些附加的類型約束。類型錯誤現在就可以在編譯時被捕獲了,而不是在運行時當作 ClassCastException 展示出來。將類型檢查從運行時挪到編譯時有助于您更容易找到錯誤,并可提高程序的可靠性。
2.2 消除強制類型轉換
泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,并且減少了出錯機會。
盡管減少強制類型轉換可以降低使用泛型類的代碼的羅嗦程度,但是聲明泛型變量會帶來相應的羅嗦。
2.3 優化了程序設計,解決了黃色警告線
3. 泛型的應用
3.1 泛型的內部原理
泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程序中的非法輸入。但是,編譯器編譯帶類型說明的集合時會去除掉“類型”信息,目的就是使程序運行效率不受影響。因此,對于參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。
package com.itheima.day2;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<String> collection1 = new ArrayList<String>();
ArrayList collection2 = new ArrayList();
System. out.println(collection1.getClass() == collection2.getClass());
//結果:true
}
}
由于編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,例如,用反射得到集合,再調用其add方法即可。
package com.itheima.day2;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) throws Exception {
ArrayList<Integer> collection1 = new ArrayList<Integer>();
collection1.getClass().getMethod( "add",Object.class).invoke(collection1, "abc");
System. out.println(collection1.get(0));
}
}
ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:
- 整個稱為ArrayList<E>泛型類型
- ArrayList<E>中的E稱為類型變量或類型參數
- 整個ArrayList<Integer>稱為參數化的類型
- ArrayList<Integer>中的Integer稱為類型參數的實例或實際類型參數
- ArrayList<Integer>中的<>念著typeof
- ArrayList稱為原始類型
參數化類型與原始類型的兼容性:參數化類型可以引用一個原始類型的對象,編譯報告警告,例如
Collection<String> c = new Vector();//考慮到對以前代碼的兼容性,編譯器是可以通過的
原始類型可以引用一個參數化類型的對象,編譯報告警告,例如
Collection c = new Vector<String>();//原來的方法接受一個集合參數,新的類型也要能傳進去
參數化類型不考慮類型參數的繼承關系:
Vector<String> v = new Vector<Object>(); //錯誤!不寫<Object>沒錯,寫了就是明知故犯
Vector<Object> v = new Vector<String>(); //也錯誤!
注意:
假設Vector<String> v = new Vector<Object>();可以的話,那么以后從v中取出的對象當作String用,而v實際指向的對象中可以加入任意的類型對象;
假設Vector<Object> v = new Vector<String>();可以的話,那么以后可以向v中加入任意的類型對象,而v實際指向的集合中只能裝String類型的對象。
編譯器不允許創建泛型變量的數組。即在創建數組實例時,數組的元素不能使用參數化的類型。
例如,下面語句有錯誤:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考題:
下面的代碼會報錯誤嗎?
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
答案:編譯的時候是不會報錯的,因為編譯器是一行一行按照語法檢查代碼的,因此不會出錯。
4. 泛型類
把泛型定義在類上,格式:public class 類名<泛型類型1,…>,注意:泛型類型必須是引用類型
package cn.itcast_04;
/*
* 泛型類的測試
*/
public class ObjectToolDemo {
public static void main(String[] args) {
// ObjectTool ot = new ObjectTool();
//
// ot.setObj(new String("風清揚"));
// String s = (String) ot.getObj();
// System.out.println("姓名是:" + s);
//
// ot.setObj(new Integer(30));
// Integer i = (Integer) ot.getObj();
// System.out.println("年齡是:" + i);
// ot.setObj(new String("林青霞"));
// // ClassCastException
// Integer ii = (Integer) ot.getObj();
// System.out.println("姓名是:" + ii);
System.out.println("-------------");
ObjectTool<String> ot = new ObjectTool<String>();
// ot.setObj(new Integer(27)); //這個時候編譯期間就過不去
ot.setObj(new String("林青霞"));
String s = ot.getObj();
System.out.println("姓名是:" + s);
ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
// ot2.setObj(new String("風清揚"));//這個時候編譯期間就過不去
ot2.setObj(new Integer(27));
Integer i = ot2.getObj();
System.out.println("年齡是:" + i);
}
}
//泛型類:把泛型定義在類上
class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
5. 泛型方法
把泛型定義在方法上,格式:public <泛型類型> 返回類型 方法名(泛型類型 .)
package cn.itcast_05;
public class ObjectToolDemo {
public static void main(String[] args) {
// ObjectTool ot = new ObjectTool();
// ot.show("hello");
// ot.show(100);
// ot.show(true);
// ObjectTool<String> ot = new ObjectTool<String>();
// ot.show("hello");
//
// ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
// ot2.show(100);
//
// ObjectTool<Boolean> ot3 = new ObjectTool<Boolean>();
// ot3.show(true);
// 定義泛型方法后
ObjectTool ot = new ObjectTool();
ot.show("hello");
ot.show(100);
ot.show(true);
}
}
//泛型方法:把泛型定義在方法上
class ObjectTool {
public <T> void show(T t) {
System.out.println(t);
}
}
6. 泛型接口
把泛型定義在接口上,格式:public interface 接口名<泛型類型1…>
package cn.itcast_06;
public class InterDemo {
public static void main(String[] args) {
// 第一種情況的測試
// Inter<String> i = new InterImpl();
// i.show("hello");
// // 第二種情況的測試
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
}
}
//泛型接口:把泛型定義在接口上
interface Inter<T> {
public abstract void show(T t);
}
// 實現類在實現接口的時候
// 第一種情況:已經知道該是什么類型的了
//public class InterImpl implements Inter<String> {
//
// @Override
// public void show(String t) {
// System.out.println(t);
// }
// }
// 第二種情況:還不知道是什么類型的
class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
7. 泛型高級(通配符)
為了解決類型被限制死了不能動態根據實例來確定的缺點,引入了“通配符泛型”,針對上面的例子,使用通配泛型格式為<? extends Collection>,“?”代表未知類型,這個類型是實現Collection接口。? extends E:向下限定,E及其子類,限定通配符的上邊界。? super E:向上限定,E及其父類,限定通配符的下邊界。
package cn.itcast_07;
import java.util.ArrayList;
import java.util.Collection;
/*
* 泛型高級(通配符)
* ?:任意類型,如果沒有明確,那么就是Object以及任意的Java類了
* ? extends E:向下限定,E及其子類
* ? super E:向上限定,E極其父類
*/
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明確的寫的時候,前后必須一致
Collection<Object> c1 = new ArrayList<Object>();
// Collection<Object> c2 = new ArrayList<Animal>();
// Collection<Object> c3 = new ArrayList<Dog>();
// Collection<Object> c4 = new ArrayList<Cat>();
// ?表示任意的類型都是可以的
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子類
// Collection<? extends Animal> c9 = new ArrayList<Object>();
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E極其父類
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();
// Collection<? super Animal> c16 = new ArrayList<Cat>();
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
泛型
泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程序中的非法輸入,編譯器編譯帶類型說明的集合時會去除掉“類型”信息,使程序運行效率不受影響,對于參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。由于編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,例如,用反射得到集合,再調用其add方法即可。
泛型引用和創建兩端,給出的泛型變量必須相同
泛型類
A<T>
Class<T> type
泛型類中使用泛型
- 成員類型
- 返回值和參數類型
- 局部變量的引用上
class A<T> {
private T bean;//泛型可在成員變量上使用
public T fun(T t) {}//泛型可以在類中的方法上(返回值和參數類型)使用!
public void fun2() {//泛型還可以在局部變量的引用類型上使用
T b = ...
new T();//不行的!
}
}
泛型方法
public <T> T add(T x, T y){
}
泛型方法與泛型類沒有什么關系,泛型方法不一定非要在泛型類中!
泛型的繼承和實現
class A<T> {
}
// AA不是泛型類,只是它爸爸是泛型類!
class AA extends A<String> {
}
繼承泛型類
- 子類不是泛型類:需要給父類傳遞類型常量
當給父類傳遞的類型常量為String時,那么在父類中所有T都會被String替換!
class AA1 extends A<String> {
}
- 子類是泛型類:可以給父類傳遞類型常量,也可以傳遞類型變量
class AA3<E> extends A<E> {
}
通配符
- 無限通配符<?>
- 向下通配符<? extends T>
- 向上通配符<? super T>
類型推斷
- 通過反射的方式獲取泛型的實際類型
- 泛型只能是引用類型,不能是基本數據類型
泛型擦除
泛型會在編譯時擦除,List<String>和List<User>這兩個的字節碼文件那一個都是List.class
泛型封裝
在你真的會用Gson嗎?Gson使用指南(一) 的第三節我介紹了在Gson中如何使用泛型來簡化我們的類設計,但隨之而來引入了一個新的問題:封裝。不知道各位有沒有想過這樣一個問題:每次都要用 new TypeToken<XXX>(){};
好麻煩,有沒有更好的辦法?
有更好的辦法么?當然有!相信也有不少人自己作了嘗試,只是有人歡喜有人愁了,不過沒關系,今天我們就來解決這個問題。
約定
1、本文涉及到的json格式
// data 為 object 的情況
{"code":"0","message":"success","data":{}}
// data 為 array 的情況
{"code":"0","message":"success","data":[]}
2、假定第一種的對應的Java類型為 Result<XXX>
,第二種為 Result<List<XXX>>
為何封裝,如何封裝
1. 為何封裝
- 寫
new TypeToken<XXX>(){}
麻煩,IDE格式化后還不好看 - 不同的地方每進行一次
new TypeToken<XXX>(){}
操作都會生成一個新的類 - 對于任意類
XXX
都只有兩種情況new TypeToken<Result<XXX>>(){}
和new TypeToken<Result<List<XXX>>>(){}
- 方便統一管理
2. 如何封裝
從上面的我們可以知道,最簡單的方法就是提供兩個方法分別對應data
為Array和Object的情況并接收一個參數,即告知XXX的類型,自動將完成new TypeToken<XXX>(){}
與new TypeToken<Result<List<XXX>>>(){}
的過程。
方法原型:
// 處理 data 為 object 的情況
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {}
// 處理 data 為 array 的情況
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz){}
為何失敗?
對于那些嘗試著封裝過的人可能都這么寫過:
public static <T> Result<List<T>> fromJsonArray(Reader reader) {
Type type = new TypeToken<Result<List<T>>>(){}.getType();
return GSON.fromJson(reader, type);
}
當然上面的寫法肯定是沒有辦法完成的,雖然代碼不會報錯,但運行結果肯定是不對的,因為這里的T
其實是一個 TypeVariable
,他在運行時并不會變成我們想要的XXX,所以通過TypeToken
得到的 泛型信息只是 "Result<List<T>>"
。
如何解決?
既然TypeToken的作用是用于獲取泛型的類,返回的類型為Type
,真正的泛型信息就是放在這個Type
里面,既然用TypeToken生成會有問題,那我們自己生成Type就行了嘛。
Type是Java中所有類型的父接口,在1.8以前是一個空接口,自1.8起多了個getTypeName()
方法,下面有ParameterizedType
、 GenericArrayType
、 WildcardType
、 TypeVariable
幾個接口,以及Class
類。這幾個接口在本次封裝過程中只會用到 ParameterizedType
,所以簡單說一下:
ParameterizedType
簡單說來就是形如“ 類型<> ”的類型,如:Map<String,User>
。下面就以 Map<String,User>
為例講一下里面各個方法的作用。
public interface ParameterizedType extends Type {
// 返回Map<String,User>里的String和User,所以這里返回[String.class,User.clas]
Type[] getActualTypeArguments();
// Map<String,User>里的Map,所以返回值是Map.class
Type getRawType();
// 用于這個泛型上中包含了內部類的情況,一般返回null
Type getOwnerType();
}
所以,知道了這里需要的泛型是怎么回事,一切都好說了,下面我們來完成之前留下的空方法。
1. 實現一個簡易的 ParameterizedType
public class ParameterizedTypeImpl implements ParameterizedType {
private final Class raw;
private final Type[] args;
public ParameterizedTypeImpl(Class raw, Type[] args) {
this.raw = raw;
this.args = args != null ? args : new Type[0];
}
@Override
public Type[] getActualTypeArguments() {
return args;
}
@Override
public Type getRawType() {
return raw;
}
@Override
public Type getOwnerType() {return null;}
}
2. 生成Gson需要的泛型
2.1 解析data是object的情況
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
Type type = new ParameterizedTypeImpl(Result.class, new Class[]{clazz});
return GSON.fromJson(reader, type);
}
2.2 解析data是array的情況
是Array的情況要比是Object的情況多那么一步。
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
// 生成List<T> 中的 List<T>
Type listType = new ParameterizedTypeImpl(List.class, new Class[]{clazz});
// 根據List<T>生成完整的Result<List<T>>
Type type = new ParameterizedTypeImpl(Result.class, new Type[]{listType});
return GSON.fromJson(reader, type);
}
本次代碼較少,不提供源碼
雖然這篇博客是以Gson為例,但從上面的內容可以看出實際上和Gson關系不大,主要的內容還是Java的泛型基礎,所以這種封裝的方法同樣適用于其它的框架。
最后借這次機會給安利一個簡易的泛型生成庫 TypeBuilder ,其最初實現的目的就是讓大家快速的生成泛型信息,同時也會作一些參數檢查,保證正確性。
用上面的代碼給大家舉個例子
public static <T> Result<List<T>> fromJsonArray(Reader reader, Class<T> clazz) {
Type type = TypeBuilder
.newInstance(Result.class)
.beginSubType(List.class)
.addTypeParam(clazz)
.endSubType()
.build();
return GSON.fromJson(reader, type);
}
public static <T> Result<T> fromJsonObject(Reader reader, Class<T> clazz) {
Type type = TypeBuilder
.newInstance(Result.class)
.addTypeParam(clazz)
.build();
return GSON.fromJson(reader, type);
}
Type
Type 是 Java 編程語言中所有類型的公共高級接口。它們包括原始類型、參數化類型、數組類型、類型變量和基本類型
ParameterizedType
ParameterizedType 表示參數化類型,如 Collection<String>
方法 | 說明 |
---|---|
Type[ ] getActualTypeArguments() | 獲取真實參數 |
public abstract class BaseProtocol<T> {
...
/**泛型解析*/
protected T parsejson(String jsonString){
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
Type[] args = genericSuperclass.getActualTypeArguments();
Type type = args[0];
return GsonUtil.changeGsonToBean(jsonString,type);
}
}