编译期间确定类型安全——泛型(Generics)
泛型是提供给Javac编译器使用的。可以限定集合中输入的类型,让编译器挡住原始程序的非法输入,编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样,由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型,ArrayList<E>中的E称为类型变量或类型参数,整个ArrayList<Integer>称为参数化的类型,ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数,ArrayList<Integer>中的<>念着typeof,ArrayList称为原始类型。
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector ();
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>() // 错误Vector<Object> v = new Vector<String>() // 也错误
类型擦除
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。 Java中的泛型类似于C++中的模板,但是这种相似性仅限于表面,Java中的泛型基本上都是在编译器这个层次来实现的。属于编译器执行类型检查和类型诊断,然后生成普通的非泛型的字节码,也就是在生成的Java字节代码中是不包含泛型中的类型信息的,使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这种实现技术称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:
- 泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class;
- 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
- 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是MyException类型的。也就无法执行与异常对应的catch语句。
实例分析
了解了类型擦除机制之后,就会明白编译器承担了全部的类型检查工作。编译器禁止某些泛型的使用方式,正是为了确保类型的安全性。以上面提到的List<Object>和List<String>为例来具体分析:
public void inspect(List<Object> list) { for (Object obj : list) { System.out.println(obj); } list.add(1); //这个操作在当前方法的上下文是合法的。 }public void test() { List<String> strs = new ArrayList<String>(); inspect(strs); //编译错误 }
这段代码中,inspect()方法接受List<Object>作为参数,当在test方法中试图传入List<String>的时候,会出现编译错误。假设这样的做法是允许的,那么在inspect方法就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List<String>的集合中却被添加了一个Integer类型的对象。这显然是违反类型安全的原则的,在某个时候肯定会抛出ClassCastException。因此,编译器禁止这样的行为。编译器会尽可能的检查可能存在的类型安全问题。对于确定是违反相关原则的地方,会给出编译错误。当编译器无法判断类型的使用是否正确的时候,会给出警告信息。
通配符与上下界
在使用泛型类的时候,既可以指定一个具体的类型,如List<String>就声明了具体的类型是String;也可以用通配符?来表示未知类型,如List<?>就声明了List中包含的元素类型是未知的。 通配符所代表的其实是一组类型,但具体的类型是未知的。List<?>所声明的就是所有类型都是可以的。但是List<?>并不等同于List<Object>。List<Object>实际上确定了List中包含的是Object及其子类,在使用的时候都可以通过Object来进行引用。而List<?>则其中所包含的元素类型是不确定。其中可能包含的是String,也可能是 Integer。如果它包含了String的话,往里面添加Integer类型的元素就是错误的。正因为类型未知,就不能通过new ArrayList<?>()的方法来创建一个新的ArrayList对象。因为编译器无法知道具体的类型是什么。但是对于 List<?>中的元素确总是可以用Object来引用的,因为虽然类型未知,但肯定是Object及其子类。考虑下面的代码:
public void wildcard(List<?> list) {list.add(1); //编译错误 }
如上所示,试图对一个带通配符的泛型类进行操作的时候,总是会出现编译错误。其原因在于通配符所表示的类型是未知的。
因为对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。 如List<? extends Number>说明List中可能包含的元素类型是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问 List<? extends Number>的时候,就可以使用Number类的intValue等方法。
开发自己的泛型类
泛型类与一般的Java类基本相同,只是在类和接口定义上多出来了用<>声明的类型参数。一个类可以有多个类型参数,如 MyClass<X, Y, Z>。 每个类型参数在声明的时候可以指定上界。所声明的类型参数在Java类中可以像一般的类型一样作为方法的参数和返回值,或是作为域和局部变量的类型。但是由于类型擦除机制,类型参数并不能用来创建对象或是作为静态变量的类型。考虑下面的泛型类中的正确和错误的用法。
class ClassTest<X extends Number, Y, Z> { private X x; private static Y y; //编译错误,不能用在静态变量中 public X getFirst() {return x; } public void wrong() { Z z = new Z(); //编译错误,不能创建对象 }}
参考资料:
http://www.infoq.com/cn/articles/cf-java-generics
转载于:https://www.cnblogs.com/shuaihua/archive/2013/01/17/2864509.html
编译期间确定类型安全——泛型(Generics)相关推荐
- java的知识点23——泛型Generics、Collection接口、List特点和常用方法、 ArrayList特点和底层实现
泛型Generics 一般通过"容器"来容纳和管理数据.程序中的"容器"就是用来容纳和管理数据. 数组就是一种容器,可以在其中放置对象或基本类型数据. 数组的优 ...
- java基础知识(七)-- 泛型(Generics )
介绍 用法: List list = new ArrayList();// 1 list .add(new Integer(12));// 2 Integer x = (Integer) list . ...
- Java 泛型(Generics) 综述
一. 引子 一般的类和方法,只能使用具体类型:要么是基本类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大. 多态 算是一种泛化机制,但对代码的约束还是太强 ...
- Java基础学习——泛型(generics)学习一
概述 在JDK 5.0,Java语言引入了好几个新的功能,其中很重要的一个就是泛型(generics). 本文就是对泛型的一个概述.你可以很熟悉其他语言中的类似结构,比如C++里的模板(templat ...
- Java基础 --- 泛型 Generics
Java基础 --- 泛型 Generics 为什么需要泛型 泛型 Bounds for Type Variable Java虚拟机如何处理泛型 --- 泛型擦除 Restrictions and L ...
- 自定义异常类: 运行期间跟编译期间的区别
1.自定义异常一: 继承RuntmeExcepyion,即运行期间异常; 由于我们自定义的是运行期间的异常,我们抛出异常,程序始终不作处理,程序编译的时候不会报错,但是运行的时候会报错 ...
- ResourceDictionary主题资源替换(二) :编译期间,替换主题资源
之前的ResourceDictionary主题资源替换(一)通过加载顺序来覆盖之前的主题资源,介绍了WPF框架对ResourceDictionary资源的合并规则. 此篇介绍一种在编译期间,实现资源替 ...
- 【TS】泛型 Generics
泛型Generics,是指在定义函数Function.接口Interface 或类 Class 时不预先指定具体的类型,而是在使用的时候再指定类型的一种特征. // 函数名后的 <> 内传 ...
- maven js css 压缩,使用wro4j和maven在编译期间压缩js和css文件(经典)
最近在对一个web系统做性能优化. 而对用到的静态资源文件的压缩整合则是前端性能优化中很重要的一环. 好处不仅在于能够减小请求的文件体积,而且能够减少浏览器的http请求数. 因为是基于java的we ...
最新文章
- html dom对象简写,js参考手册-html dom对象
- hdu-4028 The time of a day
- 事件通知方式实现的重叠I/O模型
- objective-c 编程总结(第六篇)运行时操作 - 方法交换
- win7 64 安装sp1补丁提示“客户端没有所需的特权”
- OpenGL版本与OpenGL扩展机制
- 肝火旺的人,哪些食物打死都不要碰?
- 区块链学堂——公有链、私有链、联盟链、侧链、互联链
- iOS开发-类簇(Class Cluster)
- IE Tab 让Chrome兼容IE
- 51单片机简易音乐盒(可切歌)
- Win11 Wifi消失,网络适配器黄色感叹号解决方案
- 刨根究底字符编码之四——EASCII及ISO 8859字符编码方案
- 公网与私网地址转换——NAT技术的使用小技巧,超简单!!!
- 【ArcGIS风暴】ArcGIS标注和注记的区别及用法案例详解
- 毕业实习大作业(Android-Spring Boot-MySQL 前后端分离项目 快速上手实例)
- Python:Pycharm如何使用scrapy框架做爬虫?
- 条形码、二维码、RFID优缺点,卫星定位,传感器分类及应用场景
- 锐龙 7 7840HS 性能怎么样 r77840HS相当于什么水平级别
- 曲线曲面的基本理论3之曲线的参数表示