全部博文(147)
分类: Java
2008-09-07 00:30:49
为了帮助大家更好地理解 Java 语言中的泛型,我们在这里先来对比两段实现相同功能的 GJ 代码和 Java 代码。通过观察它们的不同点来对 Java 中的泛型有个总体的把握,首先来分析一下不带泛型的 Java 代码,程序如下:
1 interface Collection { |
接口 Collection
提供了两个方法,即添加元素的方法 add(Object x)
,见第 2
行,以及返回该 Collection
的 Iterator
实例的方法
iterator()
,见第 3 行。Iterator
接口也提供了两个方法,其一就是判断是否有下一个元素的方法 hasNext()
,见第 8 行,另外就是返回下一个元素的方法
next()
,见第 7 行。LinkedList
类是对接口 Collection
的实现,它是一个含有一系列节点的链表,节点中的数据类型是 Object,这样就可以创建任意类型的节点了,比如 Byte, String
等等。上面这段程序就是用没有泛型的传统的 Java 语言编写的代码。接下来我们分析一下传统的 Java 语言是如何使用这个类的。
代码如下:
1 class Test { |
从上面的程序我们可以看出,当从一个链表中提取元素时需要进行类型转换,这些都要由程序员显式地完成。如果我们不小心从 String 类型的链表中试图提取一个 Byte 型的元素,见第 15 到第 16 行的代码,那么这将会抛出一个运行时的异常。请注意,上面这段程序可以顺利地经过编译,不会产生任何编译时的错误,因为编译器并不做类型检查,这种检查是在运行时进行的。不难发现,传统 Java 语言的这一缺陷推迟了发现程序中错误的时间,从软件工程的角度来看,这对软件的开发是非常不利的。接下来,我们讨论一下如何用 GJ 来实现同样功能的程序。源程序如下:
程序的功能并没有任何改变,只是在实现方式上使用了泛型技术。我们注意到上面程序的接口和类均带有一个类型参数 A,它被包含在一对尖括号(<
>)中,见第 1,6 和 13 行,这种表示法遵循了 C++ 中模板的表示习惯。这部分程序和上面程序的主要区别就是在
Collection
, Iterator
, 或 LinkedList
出现的地方均用 Collection
, Iterator
, 或
LinkedList
来代替,当然,第 22 行对构造函数的声明除外。
下面再来分析一下在 GJ 中是如何对这个类进行操作的,程序如下:
1 class Test { |
在这里我们可以看到,有了泛型以后,程序员并不需要进行显式的类型转换,只要赋予一个参数化的类型即可,见第 4,8 和 12 行,这是非常方便的,同时也不会因为忘记进行类型转换而产生错误。另外需要注意的就是当试图从一个字符串类型的链表里提取出一个元素,然后将它赋值给一个 Byte 型的变量时,见第 16 行,编译器将会在编译时报出错误,而不是由虚拟机在运行时报错,这是因为编译器会在编译时刻对 GJ 代码进行类型检查,此种机制有利于尽早地发现并改正错误。
类型参数的作用域是定义这个类型参数的整个类,但是不包括静态成员函数。这是因为当访问同一个静态成员函数时,同一个类的不同实例可能有不同的类型参数,所以上述提到的那个作用域不应该包括这些静态函数,否则就会引起混乱。
在 Java 语言中,我们可以将某种类型的变量赋值给其父类型所对应的变量,例如,String 是 Object 的子类型,因此,我们可以将 String 类型的变量赋值给 Object 类型的变量,甚至可以将 String [ ] 类型的变量(数组)赋值给 Object [ ] 类型的变量,即 String [ ] 是 Object [ ] 的子类型。
上述情形恐怕已经深深地印在了广大读者的脑中,对于泛型来讲,上述情形有所变化,因此请广大读者务必引起注意。为了说明这种不同,我们还是先来分析一个小例子,代码如下所示:
1 List |
上述代码的第二行将 List
赋值给了
List
,按照以往的经验,这种赋值好像是正确的,因为
List
应该是 List
的子类型。这里需要特别注意的是,这种赋值在泛型当中是不允许的!List
也不是
List
的子类型。
如果上述赋值是合理的,那么上面代码的第三行的操作将是可行的,因为 lo
是
List
,所以向其添加 Integer
类型的元素应该是完全合法的。读到此处,我们已经看到了第二行的这种赋值所潜在的危险,它破坏了泛型所带来的类型安全性。
一般情况下,如果 A 是 B 的子类型,C 是某个泛型的声明,那么 C
并不是
C
的子类型,我们也不能将 C
类型的变量赋值给
C
类型的变量。这一点和我们以前接触的父子类型关系有很大的出入,因此请读者务必引起注意。
擦出:将带有泛型的字节码转化为不带有泛型的字节码
桥:public void add (Object x);将A换成了Object类型的
注意:Java 语言中的泛型不能接受基本类型作为类型参数――它只能接受引用类型。这意味着可以定义 List