Chinaunix首页 | 论坛 | 博客
  • 博客访问: 715236
  • 博文数量: 147
  • 博客积分: 6010
  • 博客等级: 准将
  • 技术积分: 1725
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-22 10:36
文章分类

全部博文(147)

文章存档

2011年(1)

2010年(1)

2009年(35)

2008年(110)

我的朋友

分类: Java

2008-09-07 00:30:49

为了帮助大家更好地理解 Java 语言中的泛型,我们在这里先来对比两段实现相同功能的 GJ 代码和 Java 代码。通过观察它们的不同点来对 Java 中的泛型有个总体的把握,首先来分析一下不带泛型的 Java 代码,程序如下:


1	interface Collection {
2 public void add (Object x);
3 public Iterator iterator ();
4 }
5
6 interface Iterator {
7 public Object next ();
8 public boolean hasNext ();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList implements Collection {
14
15 protected class Node {
16 Object elt;
17 Node next = null;
18 Node (Object elt) { this.elt = elt; }
19 }
20
21 protected Node head = null, tail = null;
22
23 public LinkedList () {}
24
25 public void add (Object elt) {
26 if (head == null) { head = new Node(elt); tail = head; }
27 else { tail.next = new Node(elt); tail = tail.next; }
28 }
29
30 public Iterator iterator () {
31
32 return new Iterator () {
33 protected Node ptr = head;
34 public boolean hasNext () { return ptr != null; }
35 public Object next () {
36 if (ptr != null) {
37 Object elt = ptr.elt; ptr = ptr.next; return elt;
38 } else throw new NoSuchElementException ();
39 }
40 };
41 }
42 }

接口 Collection 提供了两个方法,即添加元素的方法 add(Object x),见第 2 行,以及返回该 CollectionIterator 实例的方法 iterator(),见第 3 行。Iterator 接口也提供了两个方法,其一就是判断是否有下一个元素的方法 hasNext(),见第 8 行,另外就是返回下一个元素的方法 next(),见第 7 行。LinkedList 类是对接口 Collection 的实现,它是一个含有一系列节点的链表,节点中的数据类型是 Object,这样就可以创建任意类型的节点了,比如 Byte, String 等等。上面这段程序就是用没有泛型的传统的 Java 语言编写的代码。接下来我们分析一下传统的 Java 语言是如何使用这个类的。

代码如下:


1	class Test {
2 public static void main (String[] args) {
3 // byte list
4 LinkedList xs = new LinkedList();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = (Byte)xs.iterator().next();
7 // string list
8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = (String)ys.iterator().next();
11 // string list list
12 LinkedList zss = new LinkedList();
13 zss.add(ys);
14 String z = (String)((LinkedList)zss.iterator().next()).iterator().next();
15 // string list treated as byte list
16 Byte w = (Byte)ys.iterator().next(); // run-time exception
17 }
18 }

从上面的程序我们可以看出,当从一个链表中提取元素时需要进行类型转换,这些都要由程序员显式地完成。如果我们不小心从 String 类型的链表中试图提取一个 Byte 型的元素,见第 15 到第 16 行的代码,那么这将会抛出一个运行时的异常。请注意,上面这段程序可以顺利地经过编译,不会产生任何编译时的错误,因为编译器并不做类型检查,这种检查是在运行时进行的。不难发现,传统 Java 语言的这一缺陷推迟了发现程序中错误的时间,从软件工程的角度来看,这对软件的开发是非常不利的。接下来,我们讨论一下如何用 GJ 来实现同样功能的程序。源程序如下:


1	interface Collection {
2 public void add(A x);
3 public Iterator
iterator();
4 }
5
6 interface Iterator
{
7 public A next();
8 public boolean hasNext();
9 }
10
11 class NoSuchElementException extends RuntimeException {}
12
13 class LinkedList
implements Collection {
14 protected class Node {
15 A elt;
16 Node next = null;
17 Node (A elt) { this.elt = elt; }
18 }
19
20 protected Node head = null, tail = null;
21
22 public LinkedList () {}
23
24 public void add (A elt) {
25 if (head == null) { head = new Node(elt); tail = head; }
26 else { tail.next = new Node(elt); tail = tail.next; }
27 }
28
29 public Iterator
iterator () {
30 return new Iterator
() {
31 protected Node ptr = head;
32 public boolean hasNext () { return ptr != null; }
33 public A next () {
34 if (ptr != null) {
35 A elt = ptr.elt; ptr = ptr.next; return elt;
36 } else throw new NoSuchElementException ();
37 }
38 };
39 }
40 }

程序的功能并没有任何改变,只是在实现方式上使用了泛型技术。我们注意到上面程序的接口和类均带有一个类型参数 A,它被包含在一对尖括号(< >)中,见第 1,6 和 13 行,这种表示法遵循了 C++ 中模板的表示习惯。这部分程序和上面程序的主要区别就是在 Collection, Iterator, 或 LinkedList 出现的地方均用 Collection, Iterator, 或 LinkedList 来代替,当然,第 22 行对构造函数的声明除外。

下面再来分析一下在 GJ 中是如何对这个类进行操作的,程序如下:


1	class Test {
2 public static void main (String [] args) {
3 // byte list
4 LinkedList xs = new LinkedList();
5 xs.add(new Byte(0)); xs.add(new Byte(1));
6 Byte x = xs.iterator().next();
7 // string list
8 LinkedList ys = new LinkedList();
9 ys.add("zero"); ys.add("one");
10 String y = ys.iterator().next();
11 // string list list
12 LinkedList>zss=
newLinkedList>();
13 zss.add(ys);
14 String z = zss.iterator().next().iterator().next();
15 // string list treated as byte list
16 Byte w = ys.iterator().next(); // compile-time error
17 }
18 }

在这里我们可以看到,有了泛型以后,程序员并不需要进行显式的类型转换,只要赋予一个参数化的类型即可,见第 4,8 和 12 行,这是非常方便的,同时也不会因为忘记进行类型转换而产生错误。另外需要注意的就是当试图从一个字符串类型的链表里提取出一个元素,然后将它赋值给一个 Byte 型的变量时,见第 16 行,编译器将会在编译时报出错误,而不是由虚拟机在运行时报错,这是因为编译器会在编译时刻对 GJ 代码进行类型检查,此种机制有利于尽早地发现并改正错误。

类型参数的作用域是定义这个类型参数的整个类,但是不包括静态成员函数。这是因为当访问同一个静态成员函数时,同一个类的不同实例可能有不同的类型参数,所以上述提到的那个作用域不应该包括这些静态函数,否则就会引起混乱。

在 Java 语言中,我们可以将某种类型的变量赋值给其父类型所对应的变量,例如,String 是 Object 的子类型,因此,我们可以将 String 类型的变量赋值给 Object 类型的变量,甚至可以将 String [ ] 类型的变量(数组)赋值给 Object [ ] 类型的变量,即 String [ ] 是 Object [ ] 的子类型。

上述情形恐怕已经深深地印在了广大读者的脑中,对于泛型来讲,上述情形有所变化,因此请广大读者务必引起注意。为了说明这种不同,我们还是先来分析一个小例子,代码如下所示:


1			List ls = new ArrayList(); 
2 List lo = ls;
3 lo.add(new Integer());
4 String s = ls.get(0);

上述代码的第二行将 List 赋值给了 List,按照以往的经验,这种赋值好像是正确的,因为 List 应该是 List 的子类型。这里需要特别注意的是,这种赋值在泛型当中是不允许的!List 也不是 List 的子类型。

如果上述赋值是合理的,那么上面代码的第三行的操作将是可行的,因为 loList,所以向其添加 Integer 类型的元素应该是完全合法的。读到此处,我们已经看到了第二行的这种赋值所潜在的危险,它破坏了泛型所带来的类型安全性。

一般情况下,如果 A 是 B 的子类型,C 是某个泛型的声明,那么 C 并不是 C 的子类型,我们也不能将 C 类型的变量赋值给 C 类型的变量。这一点和我们以前接触的父子类型关系有很大的出入,因此请读者务必引起注意。

擦出:将带有泛型的字节码转化为不带有泛型的字节码

桥:public void add (Object x);将A换成了Object类型的

注意:Java 语言中的泛型不能接受基本类型作为类型参数――它只能接受引用类型。这意味着可以定义 List,但是不可以定义 List
阅读(675) | 评论(0) | 转发(0) |
0

上一篇: java集合总结

下一篇:不要重复DAO

给主人留下些什么吧!~~