浏览以下内容前,请点击并阅读 声明

8 类型擦除

  为实现泛型,java编译器进行如下操作进行类型擦除:

  • 如果类型参数有限制则替换为限制的类型,如果没有则替换为Object类,变成普通的类,接口和方法。
  • 有必要时插入转换操作以保证类型安全
  • 产生桥接方法以保证继承类型的多态性

  类型的擦除确保了类型参数化后,泛型不会产生新的类,因此不会引发运行时开销。

8.1 泛型类型

  整个擦除过程中,java编译器将擦除所有的类型参数,将其替换成第一个类型的限制,如无限制,则替换成Object。

  如对以下类声明的编译:

  1. public class Node<K,V extends Number> {
  2. private K data;
  3. private V next;
  4. public Node(K data, V next) }
  5. this.data = data;
  6. this.next = next;
  7. }
  8. public K getData() { return data; }
  9. // ...
  10. }

  java编译器则会作如下替换:

  1. public class Node {
  2. //由于K是没有限制的,将其替换成Object
  3. private Object data;
  4. //而V上限为Number,将其替换成Number
  5. private Number next;
  6. //方法中的也会进行替换
  7. public Node(Object data, Number next) }
  8. this.data = data;
  9. this.next = next;
  10. }
  11. public Object getData() { return data; }
  12. // ...
  13. }

8.2泛型方法的擦除

  泛型方法的擦除与泛型类型的擦除方法相同,如上述例子中getData方法的擦除所示。

8.3类型擦除以及桥接方法的影响

  有些类型的擦除会产生你可能难以预料的情况,

  当编译一个继承一个类型参数化的父类的类或者是接口时,编译器可能需要生成一个方法,叫做桥接方法,这也是类型擦除中的一个过程,可能在stack trace中显示。

  两个继承关系的类声明如下:

  1. //泛型声明
  2. public class Node<T> {
  3. public T data;
  4. public Node(T data) { this.data = data; }
  5. public void setData(T data) {
  6. System.out.println("Node.setData");
  7. this.data = data;
  8. }
  9. }
  10. //继承上述类型参数化的类
  11. public class MyNode extends Node<Integer> {
  12. public MyNode(Integer data) { super(data); }
  13. public void setData(Integer data) {
  14. System.out.println("MyNode.setData");
  15. super.setData(data);
  16. }
  17. }

经编译器类擦除:

  1. public class Node {
  2. public Object data;
  3. public Node(Object data) { this.data = data; }
  4. public void setData(Object data) {
  5. System.out.println("Node.setData");
  6. this.data = data;
  7. }
  8. }
  9. public class MyNode extends Node {
  10. public MyNode(Integer data) { super(data); }
  11. public void setData(Integer data) {
  12. System.out.println("MyNode.setData");
  13. super.setData(data);
  14. }
  15. // 由编译器生成的桥接方法
  16. public void setData(Object data) {
  17. setData((Integer) data);
  18. }
  19. }

子类MyNode继承了setData方法签名与父类中的方法不同,为保证继承的特征,所以产生了一个桥接方法。

8.4 不可具体化类型

  可具体化的类型是指类型信息在运行时可以获得的类型,包扩基本类型,非泛型,原始类型以及无限制通配符的调用。

  不可具体化的类型是指在编译时被类型擦除移除信息的类型,是为定义为无限制通配符的泛型的调用。在运行时,无法获得不可具体化的类型的全部信息。比如List<String>和List<Number>都是不可具体化的类型,Java虚拟机在运行时无法区分这些类型。

  当一个参数化的变量引用一个不是该参数化类型的对象时,就会发生堆污染,这种情况一般发生在编译时引起unchecked警告的操作中,unchecked警告可以在编译时或者运行时产生,对于参数化的类型的操作的正确性无法验证时。在原始类型和参数化的类型混用时比较容易产生堆污染。

  一般情况下,所有的代码同时编译时会产生unchecked警告以告知可能的堆污染,而分部分编译代码时比较难发现可能潜在的堆污染。

  含有不可具体化类型的可变变量的方法,容易产生堆污染,如:

  1. public class ArrayBuilder {
  2.  
  3. public static <T> void addToList (List<T> listArg, T... elements) {
  4. for (T x : elements) {
  5. listArg.add(x);
  6. }
  7. }
  8. public static void faultyMethod(List<String>... l) {
  9. Object[] objectArray = l; // Valid
  10. objectArray[0] = Arrays.asList(42);
  11. String s = l[0].get(0); // 运行时将会抛出类转化异常
  12. }
  13. }

  注意java中不允许创建参数化的类型的数组,编译器首先将第一个方法中的elements变量转换成数组T[],再经类型擦除,elements又变成Object[] ,因此可能会导致堆污染。

  第二个方法中,经类擦除,l会转化成List[],List[]是Object[]的子类型,方法中的一二句不会有问题,第三句运行时就会抛出异常。

  声明一个含有参数化类型的参数的可变变量的方法时,编译器会产生unchecked警告,可以在非构造器的方法上声明@SafeVarargs注释,以消除警告信息,也可以使用@SuppressWarnings({"unchecked", "varargs"}) 注释,不过该注释只会消除定义方法产生的警告,而不会消除调用方法产生的警告信息。

8.5 泛型的限制

  要高效地使用泛型,还要考虑到如下限制:

  8.5.1 不能使用基本数据类型去实例化一个泛型。

  例如:

  1. class Pair<K, V> {
  2.  
  3. // ...
  4. }
  5. //一下语句将会编译出错
  6. Pair<int, char> p = new Pair<>(8, 'a');
  7. //要使用基本数据类型的封装类型,注意实参是进行了自动转化的
  8. Pair<Integer, Character> p = new Pair<>(8, 'a');
  8.5.2 不能创建一个类型参数的实例
  1. public static <E> void append(List<E> list) {
  2. E elem = new E(); // 编译时错误
  3. list.add(elem);
  4. }
  5. //上述方法的声明将编译出错
  6. //不过 可以通过Class<E>的方法newInstance创建对象
  7. public static <E> void append(List<E> list, Class<E> cls) throws Exception {
  8. E elem = cls.newInstance(); // OK
  9. list.add(elem);
  10. }
  8.5.3 不能声明类型为类型参数的静态字段

  一个类的静态字段是其所有非静态的对象共有的类级的变量,所以类型为类型参数的静态字段是不允许出现的。

  8.5.4 不能对类型参数使用转化(cast)和instanceof操作符

  因为在编译时,java编译器会擦除所有的泛型代码中的类型参数,所以在运行时无法验证某个泛型使用了哪个类型参数。

  1. public static <E> void rtti(List<E> list) {
  2. if (list instanceof ArrayList<Integer>) { //此处编译出错
  3. // ...
  4. }
  5. }

 因为运行时不会跟踪泛型的类型参数,所以运行时无法区分ArrayList<Integer>和ArrayList<String>,不过使用无限制通配符可以使用instanceof操作符:

  1. public static void rtti(List<?> list) {
  2. if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type
  3. // ...
  4. }
  5. }

  这里最多的也就是验证list变量是否属于ArrayList类型。

  一般情况下,除了使用无限制通配符,你不能对一个参数化类型进行类转化:

  1. List<Integer> li = new ArrayList<>();
  2. List<Number> ln = (List<Number>) li; // 编译出错
  3. //然而有时候编译器判断类型参数合法时,就会允许进行转化,如下所示:
  4.  
  5. List<String> l1 = ...;
  6. ArrayList<String> l2 = (ArrayList<String>)l1; // OK
  8.5.5 不能创建参数化类型的数组 
  1. List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译出错
  8.5.6 不能创建,捕获或者抛出参数化类型的对象

  泛型类不能直接或间接竭诚Throwable类,否则将编译不通过。

  方法中不能捕获一个类型参数:

  1. public static <T extends Exception, J> void execute(List<J> jobs) {
  2. try {
  3. for (J job : jobs)
  4. // ...
  5. } catch (T e) { // 编译将出错
  6. // ...
  7. }
  8. }

  然而,类型参数可用于throws语句中,如:

  1. class Parser<T extends Exception> {
  2. public void parse(File file) throws T { // OK
  3. // ...
  4. }
  5. }
  8.5.7 不能重载参数类型经类型擦除后的原始类型相同的方法

  因为编译器会对类型参数进行擦除,所以:

  1. //以下类声明将编译出错
  2. public class Example {
  3. public void print(Set<String> strSet) { }
  4. public void print(Set<Integer> intSet) { }
  5. }

  上述进行类型擦除以后,两种方法的签名将相同,其参数类型都为Set ,所以编译无法通过。

java基础-泛型3的更多相关文章

  1. 一天一个Java基础——泛型

    这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲 ...

  2. Java 基础 -- 泛型、集合、IO、反射

    package com.java.map.test; import java.util.ArrayList; import java.util.Collection; import java.util ...

  3. java基础-泛型举例详解

    泛型 泛型是JDK5.0增加的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数.这种类型参数可以在类.接口.和方法的创建中,分别被称为泛型类.泛型接口.泛型方法. 一.认识泛型 在没 ...

  4. Java基础 - 泛型详解

    2022-03-24 09:55:06 @GhostFace 泛型 什么是泛型? 来自博客 Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了&quo ...

  5. java基础 泛型

    泛型的存在,是为了使用不确定的类型. 为什么有泛型? 1. 为了提高安全 2. 提高代码的重用率 (自动 装箱,拆箱功能) 一切好处看代码: package test1; import java.la ...

  6. java基础-泛型2

    浏览以下内容前,请点击并阅读 声明 6 类型推测 java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示: //泛型方法的 ...

  7. java基础-泛型1

    浏览以下内容前,请点击并阅读 声明 泛型的使用能使类型名称作为类或者接口定义中的参数,就像一般的参数一样,使得定义的类型通用性更强. 泛型的优势: 编译具有严格的类型检查 java编译器对于泛型代码的 ...

  8. Java基础---泛型、集合框架工具类:collections和Arrays

    第一讲     泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类 ...

  9. Java基础——泛型

    一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化).使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误.它是jd ...

随机推荐

  1. nginx 反代理google

    ./configure \ --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/n ...

  2. 2 column数据构成主键的表转化为1 column为主键的表

    问题:假设有张学生成绩表(tb)如下:姓名 课程 分数张三 语文 74张三 数学 83张三 物理 93张三 德语 null李四 语文 74李四 数学 84李四 物理 94李四 英语 80想变成(得到如 ...

  3. CentOS系统IPTables防火墙中FTP规则设置

    时间 2016-04-21 10:32:15  虫虫开源 原文  http://www.sijitao.net/2403.html 主题 iptablesFTP防火墙 在设置ftp通过iptables ...

  4. 一些js

    //fixed块随滚动条滚动 window.onscroll=function(){ var scroll_left = $(window).scrollLeft(); $('#table_fixed ...

  5. BULK操作减少redo实验

    建表: create table sm_histable ( sm_id ), sm_subid ), service_type ), orgton ), orgnpi ), destton ), d ...

  6. 用open_gapps安装google play

    说明  一个开放源码脚本自动生成最新的谷歌应用程序包.对整个google play 程序框架的打包,包括一些google官方的程序.对于阉割了google ply用户来说是一个不错的选择. 使用 下载 ...

  7. airflow 部署

    环境 : ubuntu 14.04 LTS python 2.7 script: 设置环境变量: export AIRFLOW_HOME=~/airflow 安装相关依赖包: sudo apt-get ...

  8. ORACLE LINUX 6.3 + ORACLE 11.2.0.3 RAC + VBOX安装文档

    ORACLE LINUX 6.3 + ORACLE 11.2.0.3 RAC + VBOX安装文档 2015-10-21 12:51 525人阅读 评论(0) 收藏 举报  分类: Oracle RA ...

  9. 额。。万恶之源就是c

    http://blog.csdn.net/feeltouch/article/details/45155529

  10. Python 2/3 安装与运行环境设置

    Python 2/3 安装与运行环境设置: 1.Python 软件源:https://www.python.org/    下载Win版本 https://www.python.org/downloa ...