第17章 泛型
委托让方法参数化,而泛型实现了类型参数化。使用泛型,可以将类型抽象出来,从而实现操作层面的复用,让代码优美而高效。但这个优美是有前提的,那就是要掌握泛型的用法,你才能发现代码中的美——泛型的美。好吧,让我们开始这段鉴美之旅!
17.1 什么是泛型
所谓泛型,就是通过参数化类型来实现在同一份代码上操作多种数据类型的目的。将类型参数化可以实现类的行为和该行为操作的类型分开。很显然,这实现了复用,并且是类型安全的。
接下来,我们来看一段代码,它实现了一个栈,但只能处理int数据类型,如代码清单17-1所示。
代码清单17-1 一个栈的例子
1 class IntStack
2{
3 private int_size;
4 private int_pointer;
5 private int[]_db;
6
7 public IntStack(int size)
8{
9_size=size;
10_db=new int[_size];
11}
12
13 public void Push(int data)
14{
15 int tmp=_pointer++;
16 if(tmp<_size)
17{
18_db[tmp]=data;
19}
20}
21
22 public int Pop()
23{
24 return_db[—_pointer];
25}
26}
虽然,这段代码可以正常地工作,但当我们想要处理其他类型时,可能有几种选择,如表17-1所示:
接下来,我们分别使用一段示例代码,对上述两种方案进行阐述和对比。
❑方案一:这里以处理string类型为例,看看基于代码清单17-1要做哪些改动呢?如图17-1所示,可以看到,代码将会有5处修改,新的StringStack类的大部分代码和主结构均来自IntStack类,并大部分相同,主要的不同仅仅是类名和某些数据类型,前者是int类型,后者改为string类型。此外,在IntStack类中有多处成员为int类型,要修改哪些int类型为string类型需要进行仔细分析,否则会导致错误。可见,本方案主要的工作就是复制和粘贴,并做相应的改动,一不小心就会犯错。
❑方案二:自方案一改进而来,改进了方案一的一些不足,例如可以实现对于多种类型的处理,避免了为每个要处理的新类型复制一份代码,等等,如代码清单17-2所示。
代码清单17-2 ObjectStack类的示例代码
1 class ObjectStack
2{
3 private int_size;
4 private int_pointer;
5 private object[]_db;
6
7 public ObjectStack(int size)
8{
9_size=size;
10 db=new object[_size];
11}
12
13 public void Push(object data)
14{
15 int tmp=_pointer++;
16 if(tmp<_size)
17{
18_db[tmp]=data;
19}
20}
21
22 public object Pop()
23{
24 return_db[—_pointer];
25}
26}
图 17-1 在原来代码基础上新增对string类型的支持
方案二并非尽善尽美,目的虽然达到了,现在可以处理各种类型了。因为这里使用的是object类型,正因为是object类型,因此带来了一些新的问题,罗列如下:
❑如果需要处理的类型是值类型,调用Push方法将“装箱”成object类型,调用Pop方法后因为返回的是object类型,因此需要转换为原来的值类型,又再次从object类型“拆箱”为值类型,频繁地装箱、拆箱,对性能会产生一定影响。
❑如果需要处理的类型是引用类型,那么同样,调用Push方法将“向上转型”为object类型,调用Pop方法后因为返回的是object类型,因此需要进行显式类型转换。
基于这两点,方案二也不是一个好的解决方案。如果使用泛型代码,就优美多了,而且也不需要进行装箱、拆箱以及显式类型转换了。那么,请继续看使用了泛型的方案三。