前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java泛型探究及泛型擦除机制和如何跳过编译阶段

Java泛型探究及泛型擦除机制和如何跳过编译阶段

作者头像
向着百万年薪努力的小赵
发布2022-12-02 09:34:13
4900
发布2022-12-02 09:34:13
举报
文章被收录于专栏:小赵的Java学习

在工作闲暇之余,开始了对Java本身的探究,首先研究的便是日常使用的泛型

  • 泛型的原理: Java泛型是jdk5引入的一种机制。为了向下兼容,所以Java虚拟机是不支持泛型的,也就是说Java泛型是一种伪泛型机制。
  • 泛型的本质: 参数化类型

我们先来看泛型的使用,然后再看泛型的擦除机制

泛型的使用

要想使用好泛型,首先要对其基本的定义有所了解

泛型通配符的介绍
  1. 无边界通配符 举例:<?> 通用的类型
  2. 上边界通配符 举例:< ? extends Number > 代表从Number往下的子类或孙类对象都可以使用
  3. 下边界通配符 举例:<? super Integer> 代表从Integer 到Object所有的对象都可以

泛型的具体的使用

规则

必须先声明再使用

泛型的声明是通过"<>"实现

约定泛型可以使用单个大写字母来表示 K E T V 等

常见的泛型使用:

泛型类

代码语言:javascript
复制
public class PersonNew <T> {

    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public PersonNew(T t) {
        this.t = t;
    }
}

增加我们代码的灵活度

泛型方法

代码语言:javascript
复制
public class Demo07 <K,V> {

    /**
     * 普通方法 可以使用 类中定义的泛型
     * @param k
     * @param v
     * @return
     */
    public K method1(K k,V v){
        return (K)null;
    }

    /**
     * 普通方法  使用方法中定义的泛型
     * @param t
     * @param v
     * @param <T>
     * @return
     */
    public <T> T method2(T t,V v){
        return (T)null;
    }

    /**
     * 在静态方法中我们没法使用 类中定义的泛型
     * @return
     */
    public static <K> K method3(){
        return null;
    }

泛型接口

代码语言:javascript
复制
public interface CalGeneric <T> {

    T add(T a,T b);

    T sub(T a,T b);

    T mul(T a,T b);

    T div(T a,T b);
}

泛型的擦除机制

  • 泛型的擦除机制: 伪泛型机制就是说:在编译期间把泛型的信息全部擦除掉了, 泛型只在编译阶段有效,编译之后JVM会采取去泛型化的措施.所以泛型最终都变成了最原始的类型(Object); 在运行期就不存在泛型的信息。

在这里贴上两句百度上对泛型的解释:

  Java 泛型的参数只可以代表类,不能代表个别对象。   由于 Java泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。Java编译器在编译泛型时会自动加入类型转换的编码,故运行速度不会因为使用泛型而加快

  泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。   泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。 ————百度百科

  也就是说,我们日常使用的泛型,JVM并不知道它的存在,因为泛型在编译阶段就已经被处理成普通的类和方法

那么编译期是怎么擦除泛型的呢? 通过研究发现,其实就是将泛型指定为限定类型而已

编译器怎么擦除泛型的?

获取泛型的类型,再转换罢了

  • 如果没有指定类型,使用Object作为原始类型;
  • 若有限定类型< T exnteds X >,使用X作为原始类型;
  • 如果有多个限定类型(<T extends A & B & C >),则用第一个边界A作为原始类型

在必要时还会插入类型转换以保持类型安全

怎么证明编译器擦除了泛型?

举个栗子:

代码语言:javascript
复制
        List<String> arrList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();
        System.out.println(arrList.getClass()==intList.getClass());

我们创建两个指定不同类型的泛型的List,然后进行比较

在这里插入图片描述
在这里插入图片描述

呐,两个List其实就是一个实例 ArrayList 和 ArrayList 在编译的时候是完全不同的类型,但是运行结果却是true,这就Java泛型的类型擦除造成的。 我们再获取一下ClassName,输出一下看看

代码语言:javascript
复制
System.out.println(arrList.getClass().getName()+"\n"+intList.getClass().getName());

结果如下图:

在这里插入图片描述
在这里插入图片描述

因为不管是 ArrayList 还是 ArrayList,在编译完成后都会被编译器擦除成了 ArrayList。

Java 泛型擦除是 Java 泛型中的一个重要特性,其目的是避免过多的创建类而造成的运行时的过度消耗。所以,想 ArrayList 和 ArrayList 这两个实例,其类实例是同一个。

那么使用泛型的副作用有哪些呢?

  1. 使用泛型后,不能使用基本数据类型(byte,short,int ,long,float,double,boolean,char); 至于原因嘛,上面也讲了,擦除后变成Object,而Object无法存放int类型
  2. 不能使用 instanceof运算符 原因:因为擦除后只剩下原始类型,泛型信息不存在。

额外的思考: 上面已经分析过,泛型是编译阶段有效的,如果我们插入数据时想要跳过编译阶段,应该怎么做呢? 没错,可以通过反射的方式达成我们的目的

代码语言:javascript
复制
        List<String> arrList = new ArrayList<>();
        arrList.add("1");
        arrList.add("2");
        Class<? extends List> aClass = arrList.getClass();
        Method method = aClass.getDeclaredMethod("add",Object.class);
        method.invoke(arrList,new Object());
        System.out.println(arrList);
在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-08-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 泛型的使用
    • 泛型通配符的介绍
    • 泛型的具体的使用
      • 常见的泛型使用:
      • 泛型的擦除机制
        • 编译器怎么擦除泛型的?
          • 怎么证明编译器擦除了泛型?
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档