Java泛型的实现机制

参考链接:https://www.cnblogs.com/coprince/p/8603492.html       https://blog.csdn.net/seu_calvin/article/details/52230032

基本知识:

Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型只在编译阶段有效 

ArrayList<String> a = new ArrayList<String>();
ArrayList b = new ArrayList();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); //true

上面程序的输出结果为true。因为所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施即擦除机制。

也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。

通配符

为了引出通配符的概念,先看如下代码:

List<Integer> ex_int= new ArrayList<Integer>();  
List<Number> ex_num = ex_int; //非法的

上述第2行会出现编译错误,因为Integer虽然是Number的子类,但List<Integer>不是List<Number>的子类。

假定第2行代码没有问题,那么我们可以使用语句ex_num.add(newDouble())在一个List中装入了各种不同类型的子类,这显然是不可以的,因为我们在取出List中的对象时,就分不清楚到底该转型为Integer还是Double了。因此,我们需要一个在逻辑上可以用来同时表示为List<Integer>和List<Number>的父类的一个引用类型,类型通配符应运而生。在本例中表示为List<?>即可。

下面这个例子也说明了这一点,注释已经写的很清楚了。

public static void main(String[] args) {
    FX<Number> ex_num = new FX<Number>(100);
    FX<Integer> ex_int = new FX<Integer>(200);
    getData(ex_num);
    getData(ex_int);//编译错误
}
 
public static void getData(FX<Number> temp) { //此行若把Number换为“?”编译通过
    //do something...
}
    
public static class FX<T> {
    private T ob; 
    public FX(T ob) {
        this.ob = ob;
    }
}

上下边界

看了下面这个上边界的例子就明白了,下界FX<? supers Number>的形式就不做过多赘述了。

 

public static void main(String[] args) {
    FX<Number> ex_num = new FX<Number>(100);
    FX<Integer> ex_int = new FX<Integer>(200);
    getUpperNumberData(ex_num);
    getUpperNumberData(ex_int);
}
 
public static void getUpperNumberData(FX<? extends Number> temp){
      System.out.println("class type :" + temp.getClass());
}
    
public static class FX<T> {
    private T ob; 
    public FX(T ob) {
    this.ob = ob;
    }
}

泛型方法:泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

特殊情况:泛型方法出现在泛型类中:

class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
        //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

 

泛型的优势:

(1)类型安全。 

通过知道泛型定义的变量类型限制,编译器可以更有效地提高Java程序的类型安全。 

(2)消除强制类型转换。 

消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的。

(3)运行到JVM的方法区,擦除机制使其运行内存负担很小,因为运行都是要加载到方法区的,这种使其方法区的压力很小

 

泛型的缺点:

因为运行时的擦除机制导致下面两者一样,所以不能方法重载

下图中除了List<T> list = new ArrayList<T>();正确,其他都是错误的,因为在创建对象时,并不知道T是什么,没有办法创建对象/类型。

编译完成后,类型擦除掉了,所以在运行时还是需要强转,只是书写方面不用强转

通过反射拿到泛型实参,将SuperClass的泛型实参String在编译时传给SubClass,在反射时可以通过SubClass拿到此泛型实参。其主要用于框架。

附加的签名信息的实例,如下图,Gson在通过匿名内部类的形式获取Type的类,此时内部就是使用反射getGenericReturnType()的方法拿到泛型实参Interger。

在Retrofit中,也是利用反射getGenericReturnType()的方法拿到泛型实参user.

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HMP*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值