JAVA泛型的使用和深入理解

泛型

目录

泛型

为什么我们需要泛型?

泛型的使用

泛型类

泛型接口

泛型方法

限定类型变量

泛型的限制

泛型类型的继承规则

通配符

泛型的实现原理(类型擦除)

获取一个对象上的泛型类型

Gson 反序列化需要借助TypeToken


为什么我们需要泛型?

1,类型安全。

泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

2,消除强制类型转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

3,潜在的性能收益

泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。

Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

4、更好的代码复用性,比如实现泛型加法

举个例子,想要实现加法运算方法,不借助泛型需要实现float,int,double的不同参数类型的方法法,使用泛型就可以屏蔽掉float,int,double的区别,一个方法即可概括所有的参数类型。

泛型的使用

泛型类

public class NormalGeneric<K> {
    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<String> 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<T> {
    public T next();
}

泛型类实现泛型接口

public class ImplGenertor<T> implements Genertor<T> {
    @Override
    public T next() {
        return null;
    }
}

普通类实现泛型接口

public class ImplGenertor implements Genertor<String> {
    @Override
    public String next() {
        return null;
    }
}

泛型方法

泛型方法必须带有<>,如果只是在泛型类里定义的方法恰好返回了泛型类的类型的话,那么这个方法不是泛型方法

public <T> T genericMethod(T...a){
     return a[a.length/2];
}

泛型方法的解析

public class GenericMethod {
    //这个类是个泛型类,在上面已经介绍过
    public class Generic<T>{
        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
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个
     *    如:public <T,K> K showKeyName(Generic<T> container){
     *        ...
     *        }
     */
​
​
    //这也不是一个泛型方法,这就是一个普通的方法,
    // 只是使用了Generic<Number>这个泛型类做形参而已。
    public void show(Generic<Number> obj){
​
    }
​
    /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
     */
    //    public <T> T show(E ab){
    //        //
    //    }
​
    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
     }
     */
    //    public void show(T obj){
    //
    //    }
​
    public static void main(String[] args) {
​
​
    }
}
 

限定类型变量

如果在使用泛型的时候调用了某种方法,而参数的类不一定实现此方法,所以这时候要对传入的参数进行限制,下面的例子是返回较小值,此时就必须传入实现了compareTo()方法的对象,即使用<T extends Comparable>进行限制

public class ArrayAlg {
//    public static <T> T min(T a,T b){
//        if(a.comapareTo(b)>0) return a; else return b;
//    }
    public static <T extends Comparable> 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 extends class&interface1&interface2&interface3...>,继承的类必须写在第一个,&后可以跟多个接口

泛型的限制

  • 不能实例化

//报错 
T t = new T();
  • 静态方法的参数和静态域不能使用泛型,但是静态方法可以定义成泛型方法

    静态方法在 JVMClassLoder 阶段被写到内存的方法区,有且只有一份,在多线程之间对象共享,包括静态方法的类型参数在方法 区中只有一份,也是对象共享。如果使用了泛型,当多个对象调用静态方法先后注入String、Integer类型参数,那么该静态方法的类型参数最后保存的是Integer类型,此时,先前调用静态方法的对象将会因为类型不匹配,而抛出运行时异常。当然,为了杜绝这种错误,一开始就会提示不能在静态方法中使用泛型

  • 基本类型不能当做泛型使用必须用包装器类型

        泛型必须是对象,而基本类型不是对象

  • 泛型不能使用instanceof关键字

    泛型并不会改变一个泛型类的原生类型

//报错 if(restrict instanceof  Restrict<Double>)
//报错 if(restrict instanceof  Restrict<T>)
​
 Restrict<Double> restrict = new Restrict<>();
 Restrict<String> restrictString= new Restrict<>();
 System.out.println(restrict.getClass()==restrictString.getClass());/true
  • 数组不能初始化

    可以定义泛型数组但是不能初始化

Restrict<Double>[] restrictArray;//正确 可以定义
Restrict<Double>[] restricts = new Restrict<Double>[10];//报错 不能初始化
  • 泛型不能被try-catch捕获

    可以抛,但不能捕获

    /*不能捕获泛型类对象*/
    //    public <T extends Throwable> void doWork(T x){
    //        try{
    //
    //        }catch(T x){
    //            //do sth;
    //        }
    //    }
    ​
    ​
    public <T extends Throwable> void doWorkSuccess(T x) throws T{
        try{
    ​
        }catch(Throwable e){
            throw x;
        }
    }

泛型类型的继承规则

泛型类之间不会因为泛型之间的改变而改变继承关系,但是泛型类和泛型类之间可以继承

//Employee extends Worker但是下面两行代码毫无关系
Pair<Employee> employeePair = new Pair<>();
Pair<Worker> workerPair = new Pair<>();
​
//泛型类之间的继承
private static class ExtendPair<T> extends Pair<T>{
​
}

通配符

向下面这种情况就会报错,但是我们又想泛型之间的继承关系能够正确的使用,所以引入了通配符?

//class Fruit
//class Orange extend Fruit
//class Apple extend Fruit
public static void print(GenericType<Fruit> p){
    System.out.println(p.getData().getColor());
}
​
public class GenericType<T> {
    private T data;
​
    public T getData() {
        return data;
    }
​
    public void setData(T data) {
        this.data = data;
    }
}
​
GenericType<Fruit> a = new GenericType<>();
print(a);
GenericType<Orange> b = new GenericType<>();
//print(b);报错
GenericType<Apple> 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进行限制那么就等于<T extends Object>上界就是Object,如果规定了上界那么属性就会变为我们规定的上界而不是Object

//我们定义的泛型类
class Pair<T> {  
    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<T extends Number>
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<String> stringList){
        System.out.println("List");
        return "OK";
    }
​
//    public static Integer method(List<Integer> stringList){
//        System.out.println("List");
//        return 1;
//    }

如果我们的泛型继承了多个例如<T extends List&Comparable>,当调用Comparable中的方法时,会给我们的变量进行强制类型转换

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<T>.e<K> o<T>的类型
Class<?> actualTypeArgument = (Class<?>)pt.getOwnerType();

Gson 反序列化需要借助TypeToken

假设有以下bean

public class Response<T> {
    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<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
//{"data":{"result":"数据"},"code":1,"message":"成功"}

Gson反序列化

Response<Data> 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<Response<Data>>(){
    
}.getType();
Response<Data> response = gson.fromJson(json, type);
System.out.println(response.toString());

为何使用TypeToken可让Gson知道泛型的具体类型?

泛型的信息是可以通过类保存下来的,上文TypeToken中笔者声明的为内部类,泛型信息则可被保存,后续解析则可对泛型的真实类型进行解析

下面实现一个功能类似TypeToken的类

public class TypeRefrence<T> {
    Type type;
    T t;
    //protect 只允许子类和同包使用,所以用户不使用匿名内部类,直接报错
    protect TypeRefrence() {
        //获得泛型类型,为何能获取,因为类可以保存泛型信息
        Type genericSuperclass = getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        //因为类泛型可以定义多个  A<T,E..> 所以是个数组
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        type = actualTypeArguments[0];
​
    }
    public Type getType() {
        return type;
    }
}

使用上文定义的类

Type type = new TypeRefrence<Response<Data>>(){
    
}.getType();
Response<Data> 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<Response<Data>>(){}.getType();的字节码

Type type = new TypeRefrence<Response<Data>>(){}.getType();等价于写一个类继承TypeRefrence

编写类如下:

public class MyTypeToken extends TypeRefrence<Response<Data>> {
    public MyTypeToken() {
    }
}

其字节码为

// signature Lcom/enjoy/reflect/TypeRefrence<Lcom/enjoy/reflect/Response<Lcom/enjoy/reflect/Data;>;>;
// declaration: com/enjoy/reflect/MyTypeToken extends com.enjoy.reflect.TypeRefrence<com.enjoy.reflect.Response<com.enjoy.reflect.Data>>
public class com/enjoy/reflect/MyTypeToken extends com/enjoy/reflect/TypeRefrence {
    ...
}

可以看到上方存在泛型签名信息,当虚拟机扫描此字节码时,即可获取到其上的泛型信息。


原创不易,还希望各位大佬支持一下
👍 点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值