泛型不是协变的
协变:
Java 语言中的数组是协变的(covariant),也就是说,
如果 Integer 扩展了 Number (事 实也是如此),那么不仅 Integer 是 Number ,而且 Integer[] 也是 Number[] ,在要求Number[] 的地方完全可以传递或者赋予 Integer[] 。
(更 正式地说,如果 Number 是 Integer 的超类型,那么 Number[] 也是 Integer[] 的超类型)
但是,泛型并不是协变的。 如果,List<Number> 是List<Integer> 的超类型,但是,如果需要List<Integer>的时候, 并不容许传递List<Number>,它们并不等价。
不允许的理由很简单,这样会破坏要提供的类型安全泛型. 如下代码:
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
List<Number> list2=list;//编译错误.
list2.add(new Float(19.0f));
}
|
其他协变问题
数组能够协变而泛型不能协变的另外一个问题是:不能实例化泛型类型的数组: (new List<String>[3]是不合法的 ),除非类型参数是一个未绑定类型的通配符(new List<?>[3]是合法的 ).
延迟构造
因为可以擦除功能,所以List<Integer>和List<String>是同一个类,编译器在编译List<V>的时候,只生成一个类。
所以,运行时,不能区分List<Integer>和List<String>(实际上,运行时都是List,类型被擦除了),用泛型类型参数标识类型的变量的构造就成了问题。运行时缺乏类型信息,这给泛型容器类和希望创建保护性副本的泛型类提出了难题。
比如泛型类Foo:
class Foo<T>{
public void dosomething(T param){
T copy=new T(param);//语法错误.
}
}
|
因为编译的时候,还不知道要构造T的具体类型,所以也无法调用T的构造函数。
那么,我们是否可以使用克隆来构建呢?
class Foo<T extends Cloneable>{
public void dosomething(T param){
//编译错误,
T copy=(T)param.clone();
}
}
|
为什么会报错呢? 因为clone()在Object类中是受保护的。
所以,不能复制在编译时根本不知道是什么类的类型引用。
构造通配符引用
那么使用通配符类型怎么样呢? 假设要创建类型为Set<?>的参数的保护性副本。 我们来看看:
class Foo{
public void doSomething(Set<?> set){
//编译出错,你不能用通配符类型的参数调用泛型构造函数
Set<?> copy=new HashSet<?>(set);
}
}
|
但是下面的方法可以实现:
class Foo{
public void doSomething(Set<?> set){
Set<?> copy=new HashSet<Object>(set);
}
}
|
构造数组
对于我们常用的ArrayList<V> ,我们需要来探讨一下它的内部实现机制:
假设它内部管理着一个V数组,那么我们希望能在ArrayList<V>的构造函数中来初始化这个数组:
class ArrayList<V>{
V[] content;
private static final int DEFAULT_SIZE=10;
public ArrayList() {
//编译错误。
content=new V[DEFAULT_SIZE];
}
}
|
但这段代码不能工作,不能实例化用类型参数表示的类型数组。因为编译器不知道V到底是代表什么类型,所以不能实例化V数组。
Java库中的 Collections提供了一种思路,用于实现,但是非常别扭(设置连Collections的作者都这样说过.) 在Collections类编译时,会产生警告:
class ArrayList<V> {
private V[] backingArray;
public ArrayList() {
backingArray = (V[]) new Object[DEFAULT_SIZE];
}
}
|
因为泛型是通过擦除实现的, backingArray 的类型实际上就是 Object[] ,因为 Object 代替了 V 。
这意味着:实际上这个类期望 backingArray 是一个 Object 数组,但是编译器要进行额外的类型检查,以确保它包含 V 类型的对象。所以这种方法很奏效,但是非常别扭,因此不值得效仿
另外有一种方法是:
声明backingArray为Object数组,并在使用它的各个地方,强转成V[]
其他方法
最好的方法是: 向构造方法中,传入类对象,这样,在运行时,就可以知道T的值了。不采用这种方法的原因是,它无法与之前版本的Collections框架相兼容。
比如:
public class ArrayList<V> implements List<V> {
private V[] backingArray;
private Class<V> elementType;
public ArrayList(Class<V> elementType) {
this.elementType = elementType;
backingArray = (V[]) Array.newInstance(elementType, DEFAULT_LENGTH);
}
}
|
但是,这里还是有地方不是很妥当:
调用 Array.newInstance() 时会引起未经检查的类型转换。为什么呢?同样是由于向后兼容性。 Array.newInstance() 的签名是:
public static Object newInstance(Class<?> componentType, int length)
|
而不是类型安全的:
public static<T> T[] newInstance(Class<T> componentType, int length)
|
为何 Array 用这种方式进行泛化呢?同样是为了保持向后兼容。要创建基本类型的数组,如 int[] , 可以使用适当的包装器类中的 TYPE 字段调用 Array.newInstance() (对于 int ,可以传递 Integer.TYPE 作为类文字)。用 Class<T> 参数而不是 Class<?> 泛化 Array.newInstance() ,对 于引用类型有更好的类型安全,但是就不能使用 Array.newInstance() 创建基本类型数组的实例了。也许将来会为引用类型提供新的 newInstance() 版本,这样就两者兼顾了。
在这里可以看到一种模式 —— 与泛型有关的很多问题或者折衷并非来自泛型本身,而是保持和已有代码兼容的要求带来的副作用。
擦除的实现
因为泛型基本上都是在JAVA编译器中而不是运行库中实现的,所以在生成字节码的时候,差不多所有关于泛型类型的类型信息都被“擦除”了, 换句话说,编译器生成的代码与手工编写的不用泛型、检查程序类型安全后进行强制类型转换所得到的代码基本相同。与C++不同,List<Integer>和List<Number>是同一个类(虽然是不同的类型,但是都是List<?>的子类型。)
擦除意味着,一个类不能同时实现 Comparable<String>和Comparable<Number>,因为事实上,两者都在同一个接口中,指定同一个compareTo()方法。
擦除也造成了上述问题,即不能创建泛型类型的对象,因为编译器不知道要调用什么构造函数。 如果泛型类需要构造用泛型类型参数来指定类型的对象,那么构造函数应该传入类对象,并将它们保存起来,以便通过反射来创建实例。
分享到:
相关推荐
Java 理论和实践 了解泛型
泛型其实就是能够向你的类型中加入类型参数的一种能力,也称作参数化的类型或参数多态性
本文讨论泛型使用的一般问题,比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般...
个人制作且上课使用的课件,希望对大家初步了解泛型相关内容有一定的帮助。个人制作且上课使用的课件,希望对大家初步了解泛型相关内容有一定的帮助。
了解泛型,能够说出泛型的作用和优点 掌握泛型类,能够独立定义和使用泛型类 掌握泛型接口,能够独立定义和使用泛型接口 掌握泛型方法,能够独立定义并使用泛型方法 掌握类型通配符,能够正确定义类型通配符的上限和...
中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握 泛型的一般应用,编写出更简单、通用、高效的应用系统。 什么是泛型 我们在编写程序时,经常遇到两个模块的...
比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般应用,编写出更简单、通用、高效...
集合了C++多重泛型应用实例,让初学者能够了解泛型编程的魅力,为学好C++ 打下良好基础。
这是一个泛型委托的示例,主要是让初学者了解泛型委托的用法,虽然很简单,但是代码有注释,我的这个例子是在vs2010中做的,用vs08的朋友们,最好还是转一下,但是绝对能实现(本程序是控制台应用程序,适合初学者)...
如何使用泛型和泛型集合,更好的了解泛型和泛型集合
对泛型的简单举例,通过例子了解泛型这个知识点
一些简单的关于泛型的样例,它能让你基本了解泛型。。。。
集合框架及泛型的介绍和基础理解,方便大家了解集合框架及泛型。
如果要深 入了解泛型编程,请查阅相关资料。 习 题 上机实践 1.将本学期开设的课程名称加入到 HashSet 中,并使用迭代器遍历输出。 2.调试书本上 TreeSet 的例子,理解其原理。 3.完成以下实验: (1)定义一个...
Webservice传递泛型的案例,包括web服务端和mobile端。让你快速的了解Webservice和mobile传递泛型的方法。
本文内容包括:了解泛型泛型编译器问题读取网页线程池其他线程池结束语下载参考资料Java:trade_mark:CollectionsFramework是Java平台的一个重要部分。桌面和企业应用程序通常都使用该框架来聚集集合项。本文将向您...
术语表 ...我相信这是因为他们通常在了解泛型是用来解决什么问题之前,就被灌输了大量的理论和范例。结果就是你有了一个解决方案,但是却没有需要使用这个解决方案的问题。 这篇文章将尝试着改变这种
一个可以让你对Java泛型更加深入了解的下例子。
同时,因为反射会带来性能损失,因此,可根据自己需求,针对每个类型轻松在两种模式之前切换,本例源码,测试实例俱全,而且代码浅显易懂,只要对泛型、反射、三层架构有一定了解的人都能轻松学习