(二)泛型学习笔记—泛型擦除原理

(二)泛型学习笔记—泛型擦除原理泛型的内部原理就是 类型擦除 java 泛型被称为伪泛型 主要是因为在编译期间 所有的泛型信息都会被擦除掉 整个 java 泛型都是在编译器层次实现的 泛型基础知识里面测试过那个例子 colleage1 和 colleage2 的类型相同 也是因为类型擦除的原因 一 类型擦除 1 类型擦除 使用泛型的时候加上的类型参数 会在编译器在编译的时候去掉 这个过程就称为类型擦除 生成的 Java 字节码

泛型的内部原理就是:类型擦除

java泛型被称为伪泛型,主要是因为在编译期间,所有的泛型信息都会被擦除掉。整个java泛型都是在编译器层次实现的。泛型基础知识里面测试过那个例子(colleage1和colleage2的类型相同),也是因为类型擦除的原因。

一、类型擦除

1.类型擦除:

使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。(生成的Java字节码中是不包含泛型中的类型信息的)

 栗子:

public static void main(String[] args){ ArrayList 
  
    arrayList1=new ArrayList 
   
     (); arrayList1.add("caka"); ArrayList 
    
      arrayList2=new ArrayList 
     
       (); arrayList2.add(100); System.out.println("arrayList1类型为:"+arrayList1.getClass()); System.out.println("arrayList2类型为:"+arrayList2.getClass()); System.out.println(arrayList1.getClass()==arrayList2.getClass()); } 
      
     
    
  

上面这段代码的输出结果是true,说明arrayList1和arrayList2的类型在运行的时候是相同的。泛型类型String和Integer都在编译的时候被擦除掉了,只剩下了原始类型。比如ArrayList<Integer>和ArrayList

等类型,在编译后都会变成ArrayList。JVM看到的只是ArrayList,泛型附加的类型信息对JVM来说是不可见的。

2.原始类型:

原始类型顾名思义就是没有泛型的最初类型,即擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。

没有限定类型时,类型变量被擦除,原始类型为Object。

class Colleage 
   
     { private T student; public Colleage(T student){ this.student=student; } public T getStudent() { return student; } public void setStudent(T student) { this.student = student; } } 
   

上例中Colleage类在经过编译之后,就成为原始的Colleage类了,因为泛型形参T是一个无限定的类型变量,所以它的原始类型为Object。
有限定类型时,类型变量被擦除,原始类型是其限定类型。对于有多个限定的类型变量,那么原始类型就用第一个边界的类型变量来替换。

class Colleage 
    
      { 
      
    

此时Colleage的原始类型就是Comparable。

class Colleage 
    
      { 
    

此时Colleage的 原始类型就是Serializable。编译器在必要的时要向Comparable插入强制类型转换。为了提高效率, 应该将标签接口(即没有方法的接口)放在边界限定列表的末尾

调用泛型方法的时候,可以指定泛型,也可以不指定泛型。原始类型和泛型变量的类型的区分也分为两种情况:

不指定泛型的情况下,泛型变量的类型为 该方法中的几种类型的同一个父类的最小级。

指定泛型的时候,该方法中的几种类型必须是该泛型实例类型或者其子类

public class Test{ public static void main(String[] args){ /不指定泛型的时候*/ int i=Test.add(1,2); //这两个参数都是Integer,所以T为Integer类型 Number f=Test.add(1, 1.2);//这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number Object o=Test.add(1,"asd");//这两个参数一个是Integer,一个是String,所以取同一父类的最小级,为Object  /指定泛型的时候*/ int a=Test. 
     
       add(1,2);//指定了Integer,所以只能为Integer类型或者其子类 Number c=Test. 
      
        add(1,2.2); //指定为Number,所以可以为Integer和Float       } public static 
       
         T add(T x,T y){ return y;       }   } 
        
       
     

4.泛型擦除疑问

先检查,后编译。
比如上例中加入这句代码:

int b=Test2. 
      
        add(1, 2.2) 
      

就会出现编译错误,因为泛型是在编译之前就检查,检查到类型为Integer,所以2.2这个Double类型的就没法执行add。

泛型检查针对引用与该引用的对象无关

 ArrayList 
       
         arrayList1=new ArrayList(); arrayList1.add("1");//编译通过 arrayList1.add(1);//编译错误 String str1=arrayList1.get(0);//返回类型为String ArrayList arrayList2=new ArrayList 
        
          (); arrayList2.add("1");//编译通过 arrayList2.add(1);//编译通过 Object object=arrayList2.get(0);//返回类型为Object 
         
       

上例中可以看出, 类型检查是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。arrayList1是String类型的ArraryList的对象的引用。会对 arrayList1的add方法进行类型检测,integer类型不能通过编译。 arrayList2是一个ArraryList的对象,add方法可以添加String和Integer,此时对象类型为公共父类Object。

泛型中参数化类型不能继承,泛型中不允许引用传递


ArrayList 
        
           arrayList1=new ArrayList 
          ();//编译错误    
        

相当于用已经规定必须存放String类型的数组,可实际上已经被存放了Object类型的对象,这样,就会有ClassCastException了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。

学习了一个厉害的大牛泛型讲解,以ArrayList的get方法的字节码文件为例,强制类型转换是在调用get()方法的时候进行的。

5.多态下的泛型(桥方法实现父类方法重写)

类型擦除后,父类的的泛型类型全部变为了原始类型Object。当在子类中给定类型时,重写了父类的方法,事实上子类中会存在方法的类型并不是父类的Object而是我们指定的类型。所以,JVM使用桥方法来完成多态。

桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类方法的就是我们看不到的桥方法。桥方法的内部实现,就只是去调用我们自己重写的方法。


6.不能抛出也不能捕获泛型类的对象。因为异常都是在运行时捕获和抛出的,而在编译的时候,泛型信息全都会被擦除掉。

public class Problem 
        
           extends Exception{......} 
        

在异常声明中可以使用类型变量。


public static 
        
           void doWork(T t) throws T{  
        

不能声明参数化类型的数组

 Colleage 
         
           [] t = new Colleage 
          
            (10); //ERROR,擦除之后,t的类型为Colleage[] 
           
         

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数

 public class Test 
          
             {         public static T testNum;   //编译错误         public static T getValue(T testNum){ //编译错误             return null;         }     }   
          

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。无法确定这个泛型参数是何种类型,所以会出现编译错误。(静态的泛型方法是没有问题的。)
























版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/176910.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午8:44
下一篇 2026年3月26日 下午8:45


相关推荐

  • 两个路由器的有线桥接与无线桥接

    两个路由器的有线桥接与无线桥接1 有线桥接 主路由器与副路由器 lan 口相连 主路由器可以正常使用 打开 DHCP 服务 电脑连接副路由器 进入管理页面 设置 lAN 口的 IP 地址手动设置为固定 ip 要求 必须是主路由器 DHCP 池中的任意一个 但是不能与其他连在主路由器中的设备相同 以 tplink 为例 修改后点击保存 待路由器自动重启后 电脑通过新修改的 LAN 口地址再次进入副路由器管理页面 关闭副路由器的 DHCP 服务 点击保存通过网线将主路由器和副路由器的任意两个 LAN 相连即可 此时设备连接副路由器便可上网 2 无线桥接 以 t

    2026年3月20日
    2
  • Linux服务器维护常用命令

    Linux服务器维护常用命令nbsp 原贴 Linux 服务器维护常用命令 2009 年 1 月 11 日 评论 发表评论 实时查看正在执行的 sql 语句 usr sbin tcpdump ieth0 s0 l w dstport3306 strings egrep i SELECT UPDATE DEL

    2026年3月18日
    2
  • goland激活码 mac【注册码】

    goland激活码 mac【注册码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月19日
    52
  • springboot更改项目名_java 文件重命名

    springboot更改项目名_java 文件重命名整体步骤如下:1、ProjectSettings下1.1、更改project的Projectname和Projectcompileroutput。1.2、更改Modules的Name1.3、删除Artifacts下的两个打包配置(稍后会再自动生成)2、更改pom.xml的artifactId3、退出idea,找到项目路径,更改项目文件名4、然后idea再open项目第一步:第二步:第三步重新打开即可…

    2022年10月13日
    6
  • OpenSSL的Heartbleed漏洞原理及简单模拟

    OpenSSL的Heartbleed漏洞原理及简单模拟Heartbleed漏洞自从Heartbleed漏洞曝光以来,网上能看到很多相关的文章,但大部分都是写的云里雾里,本文尝试直观明了的对漏洞原理进行说明及模拟。OpenSSL是SSL协议以及一系列加密算法的开源实现,使用C语言编写。OpenSSL采用Apache开源协议,可以免费用于商业用途,在很多linux发行版和服务器中得到广泛应用。OpenSSL出现漏洞造成的影响是巨大的,Heartb

    2022年7月25日
    10
  • 矩阵卷积运算过程讲解「建议收藏」

    矩阵卷积运算过程讲解「建议收藏」在爬虫处理验证码的过程中接触到矩阵卷积运算,关于该类运算,记录一下自己的心得。理论知识在讲述卷积过程前,我们来了解一下卷积公式。根据离散二维卷积公式:其中A为被卷积矩阵,K为卷积核,B为卷积结果,该公式中,三个矩阵的排序均从0开始。现在对于上面卷积过程进行分析:我们用来做例子的A矩阵为m×m(3×3)二维矩阵(被卷积矩阵),K为n×n(2×2)的二维矩阵(卷积核)。卷……

    2025年6月28日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号