随笔 - 17  文章 - 49  trackbacks - 0
<2006年8月>
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿(1)

随笔分类(17)

随笔档案(17)

相册

最新随笔

搜索

  •  

最新评论

阅读排行榜

评论排行榜

2006 8 8 星期二

 

管中窥虎

在学习 java 1.5 的过程中,我使用了 sun 公布的 tutorial ,这份文档写的比较详尽易明,但是对于想快速了解 tiger 而且具有较好 java 基础的人来说,大篇幅的英文文档是比较耗时间和非必需的,所以我将会归纳这份文档的主要内容,在保证理解的底线上,尽力减少阅读者需要的时间。

 

在以下地址可以进入各新增语言特色介绍以及下载相关文档(若有)。

http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

 

第一道虎纹: generic -泛型 / 类属

什么是泛型

泛型让你在类这一层次上进行抽象。看看例子:

List myIntList  =   new  LinkedList();  //  1 

myIntList.add(
new  Integer( 0 ));  //  2 

Integer x 
=  (Integer) myIntList.iterator().next();  //  3 

 

 

3 句的转换类型有点麻烦吧~?编译器只能保证容器类里放的是 Object 对象,要使用他们只能这样去转换。而且这样的转换也并不是完全安全的,程序员可能犯错误,容器里的对象未必是他以为的对象。有没有办法显式地表达出程序员的意图,将该容器限制为只能保存特定类型的对象?这正是 generic -泛型的核心用意。

 

 

List  <  Integer  >  myIntList  =   new  LinkedList  <  Integer  >  ();  //  1’ 

myIntList.add(
new  Integer( 0 ));  // 2’ 

Integer x 
=  myIntList.iterator().next();  //  3’ 

 

 

这样子我们就声明了一个只放 Integer List ,我们说 List 是一个 generic Interface ,接收了一个类型参数,在上例中就是 Integer 。在初始化的时候,同样的也指定了这个类型参数。

要注意这些工作的效果不是仅仅把原来的第 3 句的转换工作省掉,而是由此让编译器确保了这个 List 在程序的任何位置任何时候都用以存放正确的类型,而原来的类型转换仅仅告诉我们在这一单点处程序员自己认为的类型。

泛型由此为程序,尤其是大型程序,带来了可读性和健壮性。

定义简单的泛型

 

 public interface List < E > 

       void add(E x); 

      Iterator 
< E > iterator(); 

}
 public interface Iterator < E > 

         E next(); 

         boolean hasNext(); 

}
 

尖括号内的标识符就是一个类型形式参数。类似于方法的参数,当你使用的时候就替换一个实际参数进去,只不过这个参数是个类型。在上面的例子中,我们就替换了一个

Integer 类型进去。

在这里稍微说一下命名的规范,定义泛型中的形式参数时,使用简洁有力又具有启发性的名字,如果可以的话用单个字母更好。避免使用小写,以免和普通的方法参数混淆。

 

泛型与子类

 

看看以下的例子语句合法吗?

 

List  <  String  >  ls  =   new  ArrayList  <  String  >  ();  //

List 
<  Object  >  lo  =  ls;  //

 

2 句是行不通的,看看以下的语句:

lo.add( new  Object());  //  3 

String s 
=  ls.get( 0 );  //  4: 试图将 Object 对象赋值给字符串对象,编译错误 

简而言之就是,原有类型的继承关系是不会反映到对应的泛型上来,在上述情况下,任何两个泛型类型都不存在继承关系。那么,习惯了面向接口编程的我们怎样去适应这种严格的使用限制呢?

 

通配符

假如我们要用一个方法把一个容器内的元素都 print 出来,可以用这样的代码来实现 :

void  printCollection(Collection c) 

         Iterator i 
=  c.iterator(); 

    for (k = 0; k < c.size(); k++

         System.out.println(i.next()); 

      }

     }
 

如果我们用新的泛型和新的

for 语句(你可以先不了解它,以后会谈到)来尝试同样的功能,以下代码可行吗?

  void printCollection(Collection < Object > c) 

  
    for (Object e : c) 

           System.out.println(e); 

         }

}

 

事实是,新的代码的使用范围非常有限,因为 Collection < Object > 就只是一个放 Object 的容器泛类,它不是 任何其他 Collection 泛类的父类!真正担任这个角色的是:

Collection < ? >

这个问号代表了未知, Collection < ? > 的元素可以是任何类型,这就是通配类型。

现在我们可以这样写:

void  printCollection(Collection  <   ?   >  c) 

  
for  (Object e : c) 

         System.out.println(e); 

      }
 
}
 

 

注意在循环里面,可以把元素赋值给一个 Object 类型,因为无论 c 里放的是什么,它肯定是一个 Object ,但向 c 里放置对象则是不安全的,因为不知道 c 的泛型是什么。如下面这样是不行的:

Collection  <   ?   >  c  =   new  ArrayList  <  String  >  (); 

c.add(
new  Object());  //  编译错误 

 

add() 方面接纳的是泛型的形式参数里描述的类型(或它的子类,希望你对此不感到混乱,呵呵。),然而此时我们只看到一个问号,我们不知道它的类型参数是什么,当然就不能放置对象进去。唯一一个例外是 null ,它是任何一个类型的对象集的一分子。

 

受限通配符

省去一些说明性的代码,以我们熟悉的几何图形家族例子来说明:

 

public   void  drawAll(List  <   ?   extends  Shape  >  shapes)   }  

 

一个 List<T> ,如果 T Shape 的子类,那么这个 List 都可以被上面这个方法接纳为参数,这个就是受限的通配符。 Shape 就称为这个通配符的上限。

看看下面的代码,怎样?

public   void  addRectangle(List  <   ?   extends  Shape  >  shapes) 

      shapes.add(
0 new  Rectangle());  //  compile-time error! 

}
 

如上的方法依然是不行的,因为我们只知道

shapes Shape 或者其子类型的容器,然而具体类型是什么不知道,它未必是 Rectangle 的父类,所以不能放置元素进去。

 

总结起来,我们要了解的事情有:

l         泛型的用意

l         泛型的几种形式(普通,通配符,受限通配符)

l         泛型与继承关系一起使用时的易错倾向,尤其是向泛型容器添加元素的情况。

 

这一篇到此为止,下一篇将进入 泛型方法等较为复杂的内容。

posted on 2006-08-08 14:48 Ye Yiliang 阅读(1097) 评论(0)  编辑  收藏 所属分类: Java

只有注册用户登录后才能发表评论。


网站导航: