【Java】 泛型擦除

这篇具有很好参考价值的文章主要介绍了【Java】 泛型擦除。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 泛型擦除的介绍

1.1 泛型擦除的原因

  1. 原因一:JDK1.5及1.5之前都是没有泛型的概念的,JDK1.5之后引入了泛型的概念并为了与之前的JDK版本兼容,所以引入了泛型擦除的概念。
  2. 原因二:若对每个泛型类型都生成不同的目标代码,现有10个不同泛型的List,就要生成10份字节码,这样会造成不仅造成代码膨胀,而且一份字节码对应一个Class对象,占据大量的内存。

1.2 泛型擦除规则

  1. 情况一:首先将 所有声明泛型的地方 都擦除,然后若 定义该泛型的地方 没有指定泛型上界,则 所有该泛型类型的变量的数据类型 在编译之后都替换为Object
    java 泛型擦除,# JavaSE,java,泛型擦除

  2. 情况二:首先将 所有声明泛型的地方 都擦除,然后若 定义该泛型的地方 指定了泛型上界,则 所有该泛型类型的变量的数据类型 在编译之后都替换为泛型上界
    java 泛型擦除,# JavaSE,java,泛型擦除

  • 例题1:
    java 泛型擦除,# JavaSE,java,泛型擦除
  • 例题2:
    java 泛型擦除,# JavaSE,java,泛型擦除

1.3 泛型擦除规则的验证

  1. 方式一:通过Class对象验证:想通过Class对象获取泛型信息,但是仅仅获取的泛型信息是占位符,并不是实际的泛型类型
    List<Integer> list = new ArrayList<Integer>();  
    Map<Integer, String> map = new HashMap<Integer, String>();  
    System.out.println(Arrays.toString(list.getClass().getTypeParameters())); // 输出:[E] 
    System.out.println(Arrays.toString(map.getClass().getTypeParameters()));  // 输出:[K, V]
    
  2. 方式二:通过反射机制验证:我们知道泛型只是用来对变量类型进行约束,这个约束只在编译阶段有效,在编译之后泛型就被擦除了。比如:
    java 泛型擦除,# JavaSE,java,泛型擦除
    因此,如果可以绕过编译阶段对泛型的约束检测,那么就可以传入任何类型的变量(因为都可以向上转型为Object类型):
    ArrayList<String> list = new ArrayList<>();
    list.add("张三");
    list.add("李四");
    
    // 通过反射绕过编译
    Class clazz = list.getClass();
    Method method = clazz.getMethod("add", Object.class); // 这里必须是Object,因为泛型擦除规则:ArrayList<E>中E没有泛型上界,所以泛型擦除后占位符E用Object代替
    method.invoke(list, 21);
    
    // 打印该集合
    System.out.println(list);
    System.out.println(list.get(2));
    
    运行结果为:
    java 泛型擦除,# JavaSE,java,泛型擦除

2. 使用反射获取被擦除泛型信息的技巧

  • 问:进行泛型擦除后的程序就能够在JDK1.5上正确执行了,那么还有必要保存原本的泛型信息吗?

  • 答:会保存。所有类会先将自己类中涉及的所有实际泛型备份放在自己类中,然后自己类再将进行泛型擦除 【超级重要!!!!!!!!!!!!!!!!!!!!!!!!!】

  • 原理及获取方式:使用了泛型的类编译为class文件时会生成一个signature字段,而原本的泛型信息就被保存在class文件中signature指向的常量池中。
    java 泛型擦除,# JavaSE,java,泛型擦除
    但是signature是一个private修饰的属性,不能直接访问,只能通过反射访问。因为class文件中会生成一些public修饰的方法能将访问signature属性并获取相关信息。所以,在实际中一般采用这些方法来获取泛型,而不是直接使用signature字段。具体方法为:
    java 泛型擦除,# JavaSE,java,泛型擦除

  • 例子:

    class A<T, ID> {  
    }  
      
    class B extends A<String, Integer> {  
    }  
      
    public class Generic {  
        public static void main(String[] args) {  
            ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();  
            
    		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
    		for(Type actualTypeArgument: actualTypeArguments) {  
    		    System.out.println(actualTypeArgument);  
    		}  
        }  
    }  
    
    // 输出:
    class java.lang.String
    class java.lang.Integer
    

3. 泛型擦除导致的两大经典问题的解决方案

泛型有3类:泛型类、泛型接口、泛型方法。但无论是哪种都会造成泛型擦除,而这也造成了问题:

  1. 问题1:由于泛型擦除,导致无法在 泛型类 中获取实际泛型类型
    解决方法:使用匿名内部类
  2. 问题2:由于泛型擦除,导致无法在 泛型接口 进行 接口回调 之后获取实际泛型类型
    解决方法:使用匿名内部类
  3. 泛型方法的泛型擦除导致的问题,目前还没有遇到,遇到了再补充!!!

3.1 在泛型类中获取实际泛型类型

根据泛型擦除规则知道类在编译之后所有泛型都会被Object或者泛型上界代替,因此导致 泛型类 中无法获取原本的泛型信息。

那么问题来了,我就是要获取原本的泛型信息该怎么办????

  • 比如:要实现以下需求
    java 泛型擦除,# JavaSE,java,泛型擦除
  • 有两种方式实现该需求,这两种方式都是借助反射实现该需求的:
    1. 方式一:将MyTest的class对象当做参数传入即可。但是这样需要修改Stream类的代码,也就是说要修改源码
      ① 分析
      java 泛型擦除,# JavaSE,java,泛型擦除
      ② 解决方法:
      java 泛型擦除,# JavaSE,java,泛型擦除
    2. 方式二:使用匿名内部类。这样不用修改Stream类的代码,也就是说不需要修改源码,只要在使用该类的地方做修改即可
      1. 分析:找一个类来继承泛型类,那么这个类就能保留泛型类的实际泛型类型:
        java 泛型擦除,# JavaSE,java,泛型擦除

        此时,以上代码从逻辑上就等价于以下代码:
        java 泛型擦除,# JavaSE,java,泛型擦除
        如果还不能理解,请运行以下代码并思考:

        class A<T, ID> {  
        }  
          
        class B extends A<String, Integer> {  
        }  
          
        public class Generic {  
            public static void main(String[] args) {  
                ParameterizedType parameterizedType = (ParameterizedType) B.class.getGenericSuperclass();  
                
        		Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();  
        		for(Type actualTypeArgument: actualTypeArguments) {  
        		    System.out.println(actualTypeArgument);  
        		}  
            }  
        }  
        
        // 输出:
        class java.lang.String
        class java.lang.Integer
        
      2. 最终解决方法:

        class Stream<E> {
            public Stream(List<E> list) {
                ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass();
                for (Type actualTypeArgument : genericSuperclass.getActualTypeArguments()) {
                    System.out.println(actualTypeArgument);
                }
            }
        }
        
        public class MyTest {
            public static void main(String[] args) throws Exception {
                ArrayList<String> list = new ArrayList<>();
                Stream<String> stream = new Stream<String>(list) {
                };
            }
        }
        

3.2 在泛型接口进行接口回调后获取实际泛型类型

问题:对于函数式接口,如果使用匿名内部类创建该接口对象的话,一般都会使用lambda表达式来代替匿名内部类。但是,在某些情况下匿名内部类不会报错,而使用lambda表达式会报错。

  • 比如:要实现以下需求
    java 泛型擦除,# JavaSE,java,泛型擦除
    • 最重要的一个点就是Lambda表达式是将对应的接口改造为对应的类,并没有构造新的类来实现泛型接口。所以泛型接口的实际泛型类型无法保存。也就是说等价于以下代码:
      java 泛型擦除,# JavaSE,java,泛型擦除
  • 有两种方式实现该需求,这两种方式都是借助反射实现该需求的:
    1. 方式一:将MyTest的class对象当做参数传入即可。但是这样需要修改FlatMapFunc类的代码,也就是说要修改源码
      java 泛型擦除,# JavaSE,java,泛型擦除

    2. 方式二:使用匿名内部类。这样不用修改Stream类的代码,也就是说不需要修改源码,只要在使用该类的地方做修改即可

      java 泛型擦除,# JavaSE,java,泛型擦除文章来源地址https://www.toymoban.com/news/detail-817181.html

到了这里,关于【Java】 泛型擦除的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 详解Java中的泛型(泛型的语法,擦除机制,泛型的上界)

    目录 一.什么是泛型 二.Java中为什么要使用泛型 三.泛型的语法 四.泛型类的使用 五.泛型的编译机制(擦除机制) 六.泛型的上界 泛型(Generics)是Java SE 5中引入的一个新特性,可以 使Java中的类和方法具有更广泛的类型范围 。通俗的说,它使得我们可以在定义类和方法时指定

    2024年02月05日
    浏览(48)
  • JavaSE——初始java

    目录 一.Java是什么 二.Java语言的特性 1. 简单性 2. 面相对象 3. 分布式(微服务) 4. 健壮性 5. 安全性 6. 体系结构中立 7. 可移植性 8. 解释性 9. 高性能 10. 多线程 11. 动态性 三.JDK环境配置 1.简介 2.安装  3.配置 4.检验 四.第一个Java程序 1.初始程序 2.运行程序 3.JDK、JRE、JVM三者之间的

    2023年04月08日
    浏览(40)
  • 【JavaSE】初识java

    目录 Java语言概述 Java是什么 Java语言重要性 语言广泛使用程度 工作领域 在校招中的岗位需求​编辑 Java语言发展简史 Java语言特性 ​编辑Java开发环境安装 初识Java的main方法 main方法示例 运行Java程序 JDK、JRE、JVM之间的关系 注释 基本规则 注释规范 标识符 ​编辑 总结

    2024年02月16日
    浏览(38)
  • 【JavaSE】java刷题--数组练习

    本篇讲解了一些数组相关题目(主要以代码的形式呈现),主要目的在于巩固数组相关知识。 上一篇 数组 讲解了一维数组和二维数组的基础知识~ 欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 思路 首先要判断 空指针和空数组 的情况,利用 字符

    2024年04月10日
    浏览(44)
  • 【JavaSE】第一个Java程序

    在JavaSE的系列中,将从第一个Java程序开始叙述,系统的把JavaSE的内容总结一次。毕竟这是第二次学习JavaSE的内容,因此感触也相对比较深一些。在JavaSE的初步计划中,大概有十一到十三篇文章,大致有:第一个Java程序、变量与运算符、流程控制、面向对象(封装、继承、多

    2024年01月22日
    浏览(39)
  • 【JavaSE】Java的反射机制

    1.java反射机制 1.1简介 被视为动态语言的关键,允许程序在执行期间,借助于RefectionAPI取得任何类的内部信息。在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个类对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方

    2024年04月26日
    浏览(41)
  • 【JavaSE】Java方法的使用

    【本节目标】 1. 掌握方法的定义以及使用 2. 掌握方法传参 3. 掌握方法重载 4. 掌握递归 目录 1.方法概念及使用 1.1什么是方法(method) 1.2 方法定义 1.3 方法调用的执行过程 1.4 实参和形参的关系 2. 方法重载 2.1 为什么需要方法重载 2.2 方法重载概念 3. 递归 3.1 生活中的故事 3.2 递

    2024年02月12日
    浏览(40)
  • Java面试整理(二)《JavaSE》

    说明:我会根据我自己的经验,给每个内容标注重要程度,共有三个等级:低、中、高。仅个人参考意见。 JVM是Java Virtual Machine的缩写,是用于运行Java字节码的虚拟机,JVM是运行在操作系统之上,这也是Java程序为什么能够运行在不同的平台或操作系统的原因。 JVM是Java语言

    2024年02月09日
    浏览(32)
  • 【JavaSE】Java基础语法(十三):Java 中的集合(十分全面)

    List (对付顺序的好帮⼿): 存储的元素是有序的、可重复的。 Set (注重独⼀⽆⼆的性质): 存储的元素是⽆序的、不可重复的。 Queue (实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。 Map (⽤ key 来搜索的专家): 使⽤键值对(key-value)存

    2024年02月10日
    浏览(42)
  • 【JavaSE】Java基础语法(十八):接口

    接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。 Java中接口存在的两个意义 用来定义规范 用来做功能的拓展 接口用interface修饰 类实现接口用implements表示 接口不能实例化 我们可以创建接口的实现类对象使用 接口的子类 要么重写接口中的所有抽

    2024年02月06日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包