目录
泛型
为什么我们需要泛型?
泛型的使用
泛型类
泛型接口
泛型方法
限定类型变量
泛型的限制
泛型类型的继承规则
通配符
泛型的实现原理(类型擦除)
获取一个对象上的泛型类型
Gson 反序列化需要借助TypeToken
1,类型安全。
泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
2,消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3,潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
4、更好的代码复用性,比如实现泛型加法
举个例子,想要实现加法运算方法,不借助泛型需要实现float,int,double的不同参数类型的方法法,使用泛型就可以屏蔽掉float,int,double的区别,一个方法即可概括所有的参数类型。
public class NormalGeneric {
private K data;
public NormalGeneric() {
}
public NormalGeneric(K data) {
this.data = data;
}
public K getData() {
return data;
}
public void setData(K data) {
this.data = data;
}
public static void main(String[] args) {
NormalGeneric normalGeneric = new NormalGeneric<>();
normalGeneric.setData("OK");
//normalGeneric.setData(1);
System.out.println(normalGeneric.getData());
NormalGeneric normalGeneric1 = new NormalGeneric();
normalGeneric1.setData(1);
normalGeneric1.setData("dsf");
}
}
可根据传入的类型,动态改变属性的类型
public interface Genertor {
public T next();
}
泛型类实现泛型接口
public class ImplGenertor implements Genertor {
@Override
public T next() {
return null;
}
}
普通类实现泛型接口
public class ImplGenertor implements Genertor {
@Override
public String next() {
return null;
}
}
泛型方法必须带有<>,如果只是在泛型类里定义的方法恰好返回了泛型类的类型的话,那么这个方法不是泛型方法
public T genericMethod(T...a){
return a[a.length/2];
}
泛型方法的解析
public class GenericMethod {
//这个类是个泛型类,在上面已经介绍过
public class Generic{
private T key;
public Generic(T key) {
this.key = key;
}
//虽然在方法中使用了泛型,但是这并不是一个泛型方法。
//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
//所以在这个方法中才可以继续使用 T 这个泛型。
public T getKey(){
return key;
}
/**
* 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
*/
// public E setKey(E key){
// this.key = key;
// }
}
/**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public K showKeyName(Generic container){
* ...
* }
*/
//这也不是一个泛型方法,这就是一个普通的方法,
// 只是使用了Generic这个泛型类做形参而已。
public void show(Generic obj){
}
/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
*/
// public T show(E ab){
// //
// }
/**
* 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
}
*/
// public void show(T obj){
//
// }
public static void main(String[] args) {
}
}
如果在使用泛型的时候调用了某种方法,而参数的类不一定实现此方法,所以这时候要对传入的参数进行限制,下面的例子是返回较小值,此时就必须传入实现了compareTo()方法的对象,即使用
public class ArrayAlg {
// public static T min(T a,T b){
// if(a.comapareTo(b)>0) return a; else return b;
// }
public static T min(T a, T b){
if(a.compareTo(b)>0) return a; else return b;
}
static class Test{}
public static void main(String[] args) {
//ArrayAlg.min(new Test(),new Test());
}
}
泛型可以继承一个类和多个接口,语法如下
不能实例化
//报错
T t = new T();
静态方法的参数和静态域不能使用泛型,但是静态方法可以定义成泛型方法
静态方法在 JVM 的 ClassLoder 阶段被写到内存的方法区,有且只有一份,在多线程之间对象共享,包括静态方法的类型参数在方法 区中只有一份,也是对象共享。如果使用了泛型,当多个对象调用静态方法先后注入String、Integer类型参数,那么该静态方法的类型参数最后保存的是Integer类型,此时,先前调用静态方法的对象将会因为类型不匹配,而抛出运行时异常。当然,为了杜绝这种错误,一开始就会提示不能在静态方法中使用泛型
基本类型不能当做泛型使用必须用包装器类型
泛型必须是对象,而基本类型不是对象
泛型不能使用instanceof关键字
泛型并不会改变一个泛型类的原生类型
//报错 if(restrict instanceof Restrict)
//报错 if(restrict instanceof Restrict)
Restrict restrict = new Restrict<>();
Restrict restrictString= new Restrict<>();
System.out.println(restrict.getClass()==restrictString.getClass());/true
数组不能初始化
可以定义泛型数组但是不能初始化
Restrict[] restrictArray;//正确 可以定义
Restrict[] restricts = new Restrict[10];//报错 不能初始化
泛型不能被try-catch捕获
可以抛,但不能捕获
/*不能捕获泛型类对象*/
// public void doWork(T x){
// try{
//
// }catch(T x){
// //do sth;
// }
// }
public void doWorkSuccess(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
泛型类之间不会因为泛型之间的改变而改变继承关系,但是泛型类和泛型类之间可以继承
//Employee extends Worker但是下面两行代码毫无关系
Pair employeePair = new Pair<>();
Pair workerPair = new Pair<>();
//泛型类之间的继承
private static class ExtendPair extends Pair{
}
向下面这种情况就会报错,但是我们又想泛型之间的继承关系能够正确的使用,所以引入了通配符?
//class Fruit
//class Orange extend Fruit
//class Apple extend Fruit
public static void print(GenericType p){
System.out.println(p.getData().getColor());
}
public class GenericType {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
GenericType a = new GenericType<>();
print(a);
GenericType b = new GenericType<>();
//print(b);报错
GenericType c = new GenericType<>();
//print(c);报错
如果想要这个继承能够实现的话即必须使用? 通配符不能使用在泛型类上
//这样定义的话上面的代码就不会报错
public static void print(GenericType extends Fruit> p){
System.out.println(p.getData().getColor());
}
通配符的上界和下界问题
//class Food
//class Fruit extend Food
//class Orange extend Fruit
//class Apple extend Fruit
//class Apple extend Hongfushi
//上界问题,在放过程中并不能确定具体哪个子类,但是取过程中上界已经规定好所以说已经确定,使得放操作报错,取操作不报错
List extends Fruit> list=new ArrayList<>();
Orange a=new Orange();
Apple b=new Apple();
Fruit c=nwe Fruit();
//list.add(a); 报错
//list.add(b); 报错
//list.add(c); 报错
Fruit c=list.get(0);//不报错
//下界问题,在放过程中能确定具体哪个子类,但是取过程中下界已经规定好所以说已经确定,使得取操作报错,放操作不报错
List super Apple> list=new ArrayList<>();
Hongfushi a=new Hongfushi();
Apple b=new Apple();
Fruit c=new Fruit();
list.add(a);
list.add(b);
//list.add(c); 报错 即使规定超类为Apple,再真实传递时也只能传递Apple的子类和Apple本身,传父类就会报错
Object o=list.get(0);//不报错 因为不能确定哪个具体的类,但是最顶层肯定是Object所以会返回一个Object
总结:如果规定上界是一定不能放数据的,取数据的时候上界一定规定所以传递哪个泛型就返回哪个泛型
规定下界是能放数据的(有限制),取数据的时候上界没有规定所以返回Object类型
在定义泛型类时,编译器会自动改变他的上界,如果你不用extends进行限制那么就等于
//我们定义的泛型类
class Pair {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
//不规定上界编译器生成的类
class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
//规定上界为Number class Pair
class Pair {
private Number value;
public Number getValue() {
return value;
}
public void setValue(Number value) {
this.value = value;
}
}
当重载方法时,这两个方法在编译器不能实现,而在jdk中是可以实现的,编译器只会判断参数类型来决定是否重载,而jdk不仅仅以参数为依据,还会以返回值为依据进行判断
public static String method(List stringList){
System.out.println("List");
return "OK";
}
// public static Integer method(List stringList){
// System.out.println("List");
// return 1;
// }
如果我们的泛型继承了多个例如
T data;
data.compareTo();//编译之后变为(Comparable)data.compareTo();
并不是说java在类型擦除的时候会完全擦除,他也会对类型进行一个Signature (弱记忆),对泛型类型进行一个记录。
Type genericType=field.getGenericType();
ParameterizedType pt = (ParameterizedType) genericType;
// 得到泛型里的class类型对象
Class> actualTypeArgument = (Class>)pt.getActualTypeArguments()[0];
// 得到<>前的类型
Class> actualTypeArgument = (Class>)pt.getRawType();
// 得到o.e o的类型
Class> actualTypeArgument = (Class>)pt.getOwnerType();
假设有以下bean类
public class Response {
T data;
int code;
String message;
@Override
public String toString() {
return "Response{" +
"data=" + data +
", code=" + code +
", message='" + message + '\'' +
'}';
}
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}
public class Data {
String result;
public Data(String result) {
this.result = result;
}
@Override
public String toString() {
return "Data{" +
"result=" + result +
'}';
}
}
Gson序列化
Response dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
//{"data":{"result":"数据"},"code":1,"message":"成功"}
Gson反序列化
Response response = gson.fromJson(json, type);
System.out.println(response.data.getClass());
//Response{data=Data{result=数据}, code=1, message='成功'}
报错,越界错误,其内部会默认为LinkedTreeMap,但是数据data的真实类型为Data,两者转换错误
java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to 。。。。
Gson在解析时需要借助TypeToken
正确使用:
Type type = new TypeToken>(){
}.getType();
Response response = gson.fromJson(json, type);
System.out.println(response.toString());
为何使用TypeToken可让Gson知道泛型的具体类型?
泛型的信息是可以通过类保存下来的,上文TypeToken中笔者声明的为内部类,泛型信息则可被保存,后续解析则可对泛型的真实类型进行解析
下面实现一个功能类似TypeToken的类
public class TypeRefrence {
Type type;
T t;
//protect 只允许子类和同包使用,所以用户不使用匿名内部类,直接报错
protect TypeRefrence() {
//获得泛型类型,为何能获取,因为类可以保存泛型信息
Type genericSuperclass = getClass().getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//因为类泛型可以定义多个 A 所以是个数组
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
type = actualTypeArguments[0];
}
public Type getType() {
return type;
}
}
使用上文定义的类
Type type = new TypeRefrence>(){
}.getType();
Response response = gson.fromJson(json, type);
System.out.println(response.toString());
//Response{data=Data{result=数据}, code=1, message='成功'}
若去掉上文的内部类的{},使用对象可否呢?
报错
java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to 。。。。
原理分析
分析Type type = new TypeRefrence
Type type = new TypeRefrence
编写类如下:
public class MyTypeToken extends TypeRefrence> {
public MyTypeToken() {
}
}
其字节码为
// signature Lcom/enjoy/reflect/TypeRefrence;>;
// declaration: com/enjoy/reflect/MyTypeToken extends com.enjoy.reflect.TypeRefrence>
public class com/enjoy/reflect/MyTypeToken extends com/enjoy/reflect/TypeRefrence {
...
}
可以看到上方存在泛型签名信息,当虚拟机扫描此字节码时,即可获取到其上的泛型信息。
✨ 原创不易,还希望各位大佬支持一下
点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!