泛型是指參數化類型:數據的類型會影響程序的行為,而這個類型參數是有邊界范圍的,叫類型限定。
Java中泛型(GenericType)從JDK1.5開始出現,在這之前的類型叫原生態類型(rawType)。
常常有人說java的泛型信息在運行期會被擦除,所以運行時無法獲取到泛型信息。這種說法是不准確的。
java中使用泛型無非兩種用法:1、聲明一個泛型類型;2、使用一個泛型類型。
如果一個類型定義了類型變量(typeVariable),那么就可以通過Class的getTypeParameters函數來獲取這些類型變量。一個類型變量有他的符號(<T>中的T)和上邊界(<T extends Object>中的Object)。類型變量的默認上邊界的Object。雖然類型變量在運行期間沒有下邊界的定義,但是如果上邊界類型使用final修飾,那么類型變量事實上的下邊界等同於上邊界(例如<T extends String>)。接下來有一段代碼示例。
public class ExampleBounds { class TypeBounds<T extends Number>{ } public static void main(String[] args){ TypeVariable[] typeVariables= TypeBounds.class.getTypeParameters(); TypeVariable typeVariable = typeVariables[0]; System.out.println("類型變量的符號是:"+typeVariable.getName()); System.out.println("類型變量的上邊界是"+typeVariable.getBounds()[0]); } }
輸出入下:
類型變量的符號是:T
類型變量的上邊界是class java.lang.Number
java是可以在編譯期間指定泛型的具體類型的(例如List<Integer> integers),且java泛型不是協變的。協變是指函數f(x)具有如下性質:如果關系s(x,y)成立,那么關系s(f(x),f(y))成立。java的數組就具有協變性質,具體表現為:Object是Integer的父類,那么Object[]是Integer[]的父類,可以用父類引用指向子類實例。而java泛型不可以,List<Object> 和 List<Integer>沒有父子關系,但是List<Object>可以通過add操作添加一個String對象。接下來有一段代碼示例。
public class Covariation { public static void main(String[] args){ Object[] objectArray = new Object[3]; Integer[] integerArray = new Integer[3]; objectArray=integerArray; List<Object> objectList = new ArrayList<Object>(); List<Integer> integerList = new ArrayList<Integer>(); //泛型不具有協變性質 //objectList=integerList; objectList.add("string value"); } }
如何理解List<Object>可以通過add操作添加一個String對象?這就用到了上面提到的類型邊界。
Java泛型在編譯期和運行期有很大的不同。運行期泛型的類型變量只有上邊界,是可變的,編譯期泛型的類型變量是確定具體類型值的:明確表示具體類型(List<Integer>)、通配符表示一個可選范圍(List<? extends Object> 和 List<? super Integer>, !!!注意:通配符?代表的類型只能有一個,但是不確定具體是哪個);同一個類型變量在編譯期的可選范圍一定是運行期間邊界范圍的子集,編譯期可選范圍只能小於等於且包含於運行期邊界范圍。使用時,編譯期間限制的類型可選范圍往往比運行期間要小得多,這也是為什么說使用泛型會更安全的原因。因為它在編譯期能比原生態類型可以更好地限制類型邊界。函數通過函數簽名和參數列表來定義的,包含有類型變量的函數定義是通過函數簽名和類型變量上邊界類型來定義的。接下來有一段代碼示例。
public class LimitBounds { public static void main(String[] args) throws Exception { List<Integer> integerList = new ArrayList<>(); List<? extends Number> upperToNumber = new ArrayList<>(); List<? super Integer> downToInteger = new ArrayList<>(); Integer integer = 1; Number number = new Double(2.0d); integerList.add(integer); // <? super Integer>中的?表示Integer的某個父類型。 // 凡是Integer的變量,一定是<? extends Integer>類型 downToInteger.add(integer); // 一個<? extends Integer>類型,並不總能表示為Integer,所以下一行代碼無法編譯 //Integer i = downToInteger.get(0); // 一個<? extends Integer>類型總能表示為Object Object o = downToInteger.get(0); // integer繼承自number,符合<? extends Number>的定義,所以可以直接這樣賦值 upperToNumber = integerList; // Integer只是<? extends Number>的一種可能,所以下一行代碼無法編譯 //upperToNumber.add(integer); // null可以是任意類型,一定是<? extends Number> upperToNumber.add(null); //<? extends Number> 類型一定可以表示為Number Number n = upperToNumber.get(0); //泛型函數使用類型上邊界來定義 Method addMethod = integerList.getClass().getMethod("add", Object.class); addMethod.invoke(integerList, "StringValue"); // 運行期List<T>的類型變量范圍只有上邊界Object for (Object value: integerList){ System.out.println(value); } } }
輸出為:
1 null StringValue
參數化類型(ParameterizedType)是指確定了類型變量的具體值的泛型。一個參數化類型的類型變量在編譯期是確定了具體值的,那么在運行期是否可以獲取這個具體的類型值?答案是不一定。當一個參數化類型被外部引用的時候(繼承、字段、函數參數、函數返回值),可以通過外部獲取到它的具體類型值。如果一個參數化類型是作為局部變量來使用,那么無法獲取到它的具體類型值。接下來有一段代碼示例。
class WithT<T> { } class Child extends WithT<Integer> { } class FieldT { public WithT<Integer> withT; public void get(WithT<Boolean> withT) { } public WithT<String> getString() { return null; } } public class Demo { public static void main(String[] args) throws Exception { Method method = FieldT.class.getMethod("getString", null); Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) returnType; Type[] typeArguments = type.getActualTypeArguments(); for (Type typeArgument : typeArguments) { Class typeArgClass = (Class) typeArgument; System.out.println("typeArgClass = " + typeArgClass); } } method = FieldT.class.getMethod("get", WithT.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { if (genericParameterType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericParameterType; Type[] parameterArgTypes = aType.getActualTypeArguments(); for (Type parameterArgType : parameterArgTypes) { Class parameterArgClass = (Class) parameterArgType; System.out.println("parameterArgClass = " + parameterArgClass); } } } Field field = FieldT.class.getField("withT"); Type genericFieldType = field.getGenericType(); if (genericFieldType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericFieldType; Type[] fieldArgTypes = aType.getActualTypeArguments(); for (Type fieldArgType : fieldArgTypes) { Class fieldArgClass = (Class) fieldArgType; System.out.println("fieldArgClass = " + fieldArgClass); } } Type genericSuper = Child.class.getGenericSuperclass(); if (genericSuper instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericSuper; Type[] fieldArgTypes = aType.getActualTypeArguments(); for (Type fieldArgType : fieldArgTypes) { Class fieldArgClass = (Class) fieldArgType; System.out.println("superArgClass = " + fieldArgClass); } } } }
輸出為:
typeArgClass = class java.lang.String parameterArgClass = class java.lang.Boolean fieldArgClass = class java.lang.Integer superArgClass = class java.lang.Integer
例子:通過Gson反序列化出一個泛型實例。
package example; /** * Description * <p> * </p> * DATE 2019/4/18. * * @author Kong Yuhang. */ class ResponseData<T> { private T data; private Integer code; private String errorMsg; public T getData() { return data; } public void setData(T data) { this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } }
package example; /** * Description * <p> * </p> * DATE 2019/4/18. * * @author Kong Yuhang. */ class Person { public String name; public Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return name + age; } }
package example; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import com.google.gson.Gson; /** * Description * <p> * </p> * DATE 2019/4/18. * * @author Kong Yuhang. */ public class UseParameterizedType { static class ParameterizedResponse implements ParameterizedType{ private Type actualType; public ParameterizedResponse(Type actualType){ this.actualType = actualType; } @Override public Type[] getActualTypeArguments() { return new Type[]{actualType}; } @Override public Type getRawType() { return ResponseData.class; } @Override public Type getOwnerType() { return null; } public Type getTypeVar(){ return actualType; } @Override public boolean equals(Object obj) { if(!(obj instanceof ParameterizedResponse)){ return false; } ParameterizedResponse another = (ParameterizedResponse)obj; if(!getRawType().equals(another.getRawType())){ return false; } return getTypeVar().equals(another.getTypeVar()); } } public static void main(String[]args){ String data = sendRequest(null); // "{\"data\":{\"name\":\"xiaoming\",\"age\":20},\"code\":200}"; Person person = dataFromResponse(data, Person.class); if(person!=null){ System.out.println(" name is = " + person.getName()); System.out.println(" age is = " + person.getAge()); } } public static <T> T dataFromResponse( String responseJson ,Class<T> clazz){ ResponseData responseData = new Gson().fromJson( responseJson, new ParameterizedResponse(clazz)); System.out.println(responseData); if(responseData.getCode()!=200){ System.out.println(" check code failed with error message : "+ responseData.getErrorMsg()); return null; } return (T)responseData.getData(); } public static String sendRequest(String url){ ResponseData<Person> data = new ResponseData<>(); Person person = new Person(); person.setAge(20); person.setName("xiaoming"); data.setData(person); data.setCode(200); return new Gson().toJson(data); } }
輸出:
example.ResponseData@5cb0d902 name is = xiaoming age is = 20