泛型是指参数化类型:数据的类型会影响程序的行为,而这个类型参数是有边界范围的,叫类型限定。
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