欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040244
“如果一个程序只含有数量固定的对象,而且已知它们的存在时间,那么这个程序可以说是相当简单的。”
通常,我们的程序需要根据程序运行时才知道的一些标准创建新对象。若非程序正式运行,否则我们根本不知道自己到底需要多少数量的对象,甚至不知道它们的准确类型。为了满足常规编程的需要,我们要求能在任何时候、任何地点创建任意数量的对象。所以不可依赖一个已命名的句柄来容纳自己的每一个对象,就象下面这样:
MyObject myHandle;
因为根本不知道自己实际需要多少这样的东西。
为解决这个非常关键的问题,Java提供了容纳对象(或者对象的句柄)的多种方式。其中内建的类型是数组。此外,Java 的工具(实用程序)库提供了一些“集合类”(亦称作“容器类”,但该术语已由AWT使用,所以这里仍采用“集合”这一称呼)。利用这些集合类,我们可以容纳乃至操纵自己的对象。
1 数组
我们已知道自己该如何定义及初始化一个数组。数组只是容纳对象的一种方式。但由于还有其他大量方法可容纳数组,所以是哪些地方使数组显得如此特别呢?
有两方面的问题将数组与其他集合类型区分开来:效率和类型。对于Java 来说,为保存和访问一系列对象(实际是对象的句柄)数组,最有效的方法莫过于数组。数组实际代表一个简单的线性序列,它使得元素的访问速度非常快,但我们却要为这种速度付出代价:创建一个数组对象时,它的大小是固定的,而且不可在那个数组对象的“存在时间”内发生改变。可创建特定大小的一个数组,然后假如用光了存储空间,就再创建一个新数组,将所有句柄从旧数组移到新数组。这属于“矢量”(Vector)类的行为。然而,由于为这种大小的灵活性要付出较大的代价,所以我们认为矢量的效率并没有数组高。
C++的矢量类知道自己容纳的是什么类型的对象,但同 Java 的数组相比,它却有一个明显的缺点:C++矢量类的operator[]不能进行范围检查,所以很容易超出边界(然而,它可以查询 vector 有多大,而且at()方法确实能进行范围检查)。在Java 中,无论使用的是数组还是集合,都会进行范围检查——若超过边界,就会获得一个RuntimeException(运行期违例)错误。这类违例指出的是一个程序员错误,所以不需要在代码中检查它。在另一方面,由于
C++的vector不进行范围检查,所以访问速度较快——在Java 中,由于对数组和集合都要进行范围检查,所以对性能有一定的影响。
另外几种常见的集合类:Vector(矢量)、Stack(堆栈)以及Hashtable(散列表)。这些类都涉及对对象的处理——好象它们没有特定的类型。换言之,它们将其当作
Object 类型处理(Object类型是Java 中所有类的“根”类)。从某个角度看,这种处理方法是非常合理的:我们仅需构建一个集合,然后任何Java 对象都可以进入那个集合(除基本数据类型外——可用Java 的基本类型封装类将其作为常数置入集合,或者将其封装到自己的类内,作为可以变化的值使用)。这再一次反映了数组优于常规集合:创建一个数组时,可令其容纳一种特定的类型。这意味着可进行编译期类型检查,预防自己设置了错误的类型,或者错误指定了准备提取的类型。当然,在编译期或者运行期,Java 会防止我们将不当的消息发给一个对象。
所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目的也就达到了。此外,用户很少会对一次违例事件感到非常惊讶的。
考虑到执行效率和类型检查,应尽可能地采用数组。然而,当我们试图解决一个更常规的问题时,数组的局限也可能显得非常明显。在研究过数组以后,将把重点放到Java 提供的集合类身上。
1.1 数组和第一类对象
无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。那些对象本身是在内存“堆”里创建的。堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new表达式)。堆对象的一部分(实际是我们能访问的唯一字段或方法)是只读的length(长度)成员,它告诉我们那个数组对象里最多能容纳多少元素。对于数组对象,“[]”语法是我们能采用的唯一另类访问方法。
下面这个例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。它也揭示出对象数组和基本数据类型数组在使用方法上几乎是完全一致的。唯一的差别在于对象数组容纳的是句柄,而基本数据类型数组容纳的是具体的数值:
示例如下:
class Weeble {
} // A small mythical creature
publicclass ArraySize {
publicstaticvoid
main(String[] args) {
// Arrays of objects:
Weeble[]a;// Null handle
Weeble[]b =new
Weeble[5];// Null handles
Weeble[]c =new
Weeble[4];
for (inti
= 0; i <c.length;i++)
c[i] =new
Weeble();
Weeble[]d = {new
Weeble(),new Weeble(),new Weeble() };
// Compile error:variable a not initialized:
//!System.out.println("a.length=" + a.length);
System.out.println("b.length
=" + b.length);
// The handles insidethe array are
// automaticallyinitialized to null:
for (inti
= 0; i <b.length;i++)
System.out.println("b["
+ i +"]=" +b[i]);
System.out.println("c.length
=" + c.length);
System.out.println("d.length
=" + d.length);
a =d;
System.out.println("a.length
=" + a.length);
// Java 1.1initialization syntax:
a =new
Weeble[] {new Weeble(),new Weeble() };
System.out.println("a.length
=" + a.length);
// Arrays ofprimitives:
int[]e;//
Null handle
int[]f
=newint[5];
int[]g
=newint[4];
for (inti
= 0; i <g.length;i++)
g[i] =i
*i;
int[]h
= { 11, 47, 93 };
// Compile error:variable e not initialized:
//!System.out.println("e.length=" + e.length);
System.out.println("f.length
=" + f.length);
// The primitivesinside the array are
// automaticallyinitialized to zero:
for (inti
= 0; i <f.length;i++)
System.out.println("f["
+ i +"]=" +f[i]);
System.out.println("g.length
=" + g.length);
System.out.println("h.length
=" + h.length);
e =h;
System.out.println("e.length
=" + e.length);
// Java 1.1initialization syntax:
e =newint[]
{ 1, 2 };
System.out.println("e.length
=" + e.length);
}
} // /:~
输出如下:
b.length= 5
b[0]=null
b[1]=null
b[2]=null
b[3]=null
b[4]=null
c.length= 4
d.length= 3
a.length= 3
a.length= 2
f.length= 5
f[0]=0
f[1]=0
f[2]=0
f[3]=0
f[4]=0
g.length= 4
h.length= 3
e.length= 3
e.length= 2
其中,数组 a只是初始化成一个 null 句柄。此时,编译器会禁止我们对这个句柄作任何实际操作,除非已正确地初始化了它。数组 b被初始化成指向由Weeble 句柄构成的一个数组,但那个数组里实际并未放置任何Weeble对象。然而,我们仍然可以查询那个数组的大小,因为b指向的是一个合法对象。这也为我们带来了一个难题:不可知道那个数组里实际包含了多少个元素,因为 length只告诉我们可将多少元素置入那个数组。换言之,我们只知道数组对象的大小或容量,不知其实际容纳了多少个元素。尽管如此,由于数组对象
在创建之初会自动初始化成null,所以可检查它是否为 null,判断一个特定的数组“空位”是否容纳一个对象。类似地,由基本数据类型构成的数组会自动初始化成零(针对数值类型)、null(字符类型)或者false(布尔类型)。
数组c 显示出我们首先创建一个数组对象,再将Weeble 对象赋给那个数组的所有“空位”。数组d 揭示出“集合初始化”语法,从而创建数组对象(用 new命令明确进行,类似于数组c),然后用Weeble 对象进行初始化,全部工作在一条语句里完成。
下面这个表达式:
a = d;
向我们展示了如何取得同一个数组对象连接的句柄,然后将其赋给另一个数组对象,就象我们针对对象句柄的其他任何类型做的那样。现在,a和d 都指向内存堆内同样的数组对象。
Java 1.1 加入了一种新的数组初始化语法,可将其想象成“动态集合初始化”。由 d 采用的Java 1.0 集合初始化方法则必须在定义d 的同时进行。但若采用 Java 1.1 的语法,却可以在任何地方创建和初始化一个数组对象。例如,假设hide()方法用于取得一个Weeble 对象数组,那么调用它时传统的方法是:
hide(d);
但在Java 1.1中,亦可动态创建想作为参数传递的数组,如下所示:
hide(new Weeble[] {new Weeble(), newWeeble() });
这一新式语法使我们在某些场合下写代码更方便了。
对于由基本数据类型构成的数组,它们的运作方式与对象数组极为相似,只是前者直接包容了基本类型的数据值。
1.1.1 基本数据类型集合
集合类只能容纳对象句柄。但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的句柄。利用象 Integer、Double之类的“封装器”类,可将基本数据类型的值置入一个集合里。用于基本数据类型的封装器类只是在某些场合下才能发挥作用。
无论将基本类型的数据置入数组,还是将其封装进入位于集合的一个类内,都涉及到执行效率的问题。显然,若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
当然,假如准备一种基本数据类型,同时又想要集合的灵活性(在需要的时候可自动扩展,腾出更多的空间),就不宜使用数组,必须使用由封装的数据构成的一个集合。大家或许认为针对每种基本数据类型,都应有一种特殊类型的Vector。但Java 并未提供这一特性。某些形式的建模机制或许会在某一天帮助 Java 更好地解决这个问题。这个是 C++比Java 做得好的一个地方,因为C++通过template 关键字提供了对“参数化类型”的支持。
1.2 数组的返回
假定我们现在想写一个方法,同时不希望它仅仅返回一样东西,而是想返回一系列东西。此时,象C 和C++这样的语言会使问题复杂化,因为我们不能返回一个数组,只能返回指向数组的一个指针。这样就非常麻烦,因为很难控制数组的“存在时间”,它很容易造成内存“漏洞”的出现。
Java 采用的是类似的方法,但我们能“返回一个数组”。当然,此时返回的实际仍是指向数组的指针。但在Java 里,我们永远不必担心那个数组的是否可用——只要需要,它就会自动存在。而且垃圾收集器会在我们完成后自动将其清除。
请思考如何返回一个字串数组:
publicclass IceCream {
static String[]flav
= {"Chocolate","Strawberry","Vanilla
Fudge Swirl",
"Mint Chip","Mocha Almond Fudge","Rum
Raisin","Praline Cream",
"Mud Pie" };
static String[] flavorSet(intn)
{
// Force it to bepositive & within bounds:
n = Math.abs(n)
% (flav.length + 1);
String[]results =new
String[n];
int[]picks
=newint[n];
for (inti
= 0; i <picks.length;i++)
picks[i] = -1;
for (inti
= 0; i <picks.length;i++)
{
retry:while (true)
{
intt = (int)
(Math.random() * flav.length);
for (intj
= 0; j <i;j++)
if (picks[j]
== t)
continue retry;
picks[i] =t;
results[i] =flav[t];
break;
}
}
returnresults;
}
publicstaticvoid
main(String[] args) {
for (inti
= 0; i < 20;i++) {
System.out.println("flavorSet("
+ i +") = ");
String[]fl =flavorSet(flav.length);
for (intj
= 0; j <fl.length;j++)
System.out.println("\t"
+ fl[j]);
}
}
} // /:~
输出:
flavorSet(0)=
Chocolate
Vanilla Fudge Swirl
Praline Cream
Mint Chip
Strawberry
Mocha Almond Fudge
Rum Raisin
Mud Pie
flavorSet(1)=
Rum Raisin
Vanilla Fudge Swirl
Mud Pie
Chocolate
Strawberry
Mint Chip
Praline Cream
Mocha Almond Fudge
flavorSet(2)=
Vanilla Fudge Swirl
Rum Raisin
Mocha Almond Fudge
Praline Cream
Mint Chip
Mud Pie
Strawberry
Chocolate
flavorSet(3)=
Mocha Almond Fudge
Praline Cream
Strawberry
Rum Raisin
Mint Chip
Chocolate
Vanilla Fudge Swirl
Mud Pie
flavorSet(4)=
Mocha Almond Fudge
Mint Chip
Vanilla Fudge Swirl
Praline Cream
Strawberry
Rum Raisin
Mud Pie
Chocolate
flavorSet(5)=
Vanilla Fudge Swirl
Mud Pie
Chocolate
Praline Cream
Strawberry
Rum Raisin
Mocha Almond Fudge
Mint Chip
flavorSet(6)=
Rum Raisin
Chocolate
Mud Pie
Praline Cream
Mint Chip
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
flavorSet(7)=
Vanilla Fudge Swirl
Praline Cream
Mocha Almond Fudge
Mint Chip
Chocolate
Strawberry
Mud Pie
Rum Raisin
flavorSet(8)=
Mocha Almond Fudge
Rum Raisin
Mud Pie
Praline Cream
Chocolate
Mint Chip
Strawberry
Vanilla Fudge Swirl
flavorSet(9)=
Mint Chip
Chocolate
Praline Cream
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
Mud Pie
Rum Raisin
flavorSet(10)=
Chocolate
Mud Pie
Strawberry
Mocha Almond Fudge
Rum Raisin
Mint Chip
Praline Cream
Vanilla Fudge Swirl
flavorSet(11)=
Rum Raisin
Chocolate
Vanilla Fudge Swirl
Strawberry
Praline Cream
Mocha Almond Fudge
Mint Chip
Mud Pie
flavorSet(12)=
Chocolate
Mocha Almond Fudge
Mud Pie
Vanilla Fudge Swirl
Strawberry
Rum Raisin
Mint Chip
Praline Cream
flavorSet(13)=
Mocha Almond Fudge
Chocolate
Praline Cream
Strawberry
Mint Chip
Vanilla Fudge Swirl
Rum Raisin
Mud Pie
flavorSet(14)=
Mud Pie
Mint Chip
Praline Cream
Rum Raisin
Chocolate
Vanilla Fudge Swirl
Strawberry
Mocha Almond Fudge
flavorSet(15)=
Chocolate
Vanilla Fudge Swirl
Mocha Almond Fudge
Praline Cream
Strawberry
Rum Raisin
Mint Chip
Mud Pie
flavorSet(16)=
Mint Chip
Mud Pie
Chocolate
Mocha Almond Fudge
Vanilla Fudge Swirl
Praline Cream
Strawberry
Rum Raisin
flavorSet(17)=
Vanilla Fudge Swirl
Strawberry
Mint Chip
Mud Pie
Rum Raisin
Mocha Almond Fudge
Chocolate
Praline Cream
flavorSet(18)=
Chocolate
Mint Chip
Mud Pie
Mocha Almond Fudge
Vanilla Fudge Swirl
Strawberry
Praline Cream
Rum Raisin
flavorSet(19)=
Chocolate
Rum Raisin
Mint Chip
Vanilla Fudge Swirl
Mud Pie
Strawberry
Praline Cream
Mocha Almond Fudge
flavorSet()方法创建了一个名为results的String 数组。该数组的大小为 n——具体数值取决于我们传递给方法的自变量。随后,它从数组 flav 里随机挑选一些“香料”(Flavor),并将它们置入 results里,并最终返回results。返回数组与返回其他任何对象没什么区别——最终返回的都是一个句柄。至于数组到底是在flavorSet()里创建的,还是在其他什么地方创建的,这个问题并不重要,因为反正返回的仅是一个句柄。一旦我们的操作完成,垃圾收集器会自动关照数组的清除工作。而且只要我们需要数组,它就会乖乖地听候调遣。
另一方面,注意当flavorSet()随机挑选香料的时候,它需要保证以前出现过的一次随机选择不会再次出现。为达到这个目的,它使用了一个无限while 循环,不断地作出随机选择,直到发现未在picks 数组里出现过的一个元素为止(当然,也可以进行字串比较,检查随机选择是否在 results数组里出现过,但字串比较的效率比较低)。若成功,就添加这个元素,并中断循环(break),再查找下一个(i 值会递增)。但假若t 是一个已在 picks 里出现过的数组,就用标签式的continue
往回跳两级,强制选择一个新 t。用一个调试程序可以很清楚地看到这个过程。
main()能显示出20个完整的香料集合,所以我们看到 flavorSet()每次都用一个随机顺序选择香料。为体会这一点,最简单的方法就是将输出重导向进入一个文件,然后直接观看这个文件的内容。
2 集合
为容纳一组对象,最适宜的选择应当是数组。而且假如容纳的是一系列基本数据类型,更是必须采用数组。
当我们编写程序时,通常并不能确切地知道最终需要多少个对象。有些时候甚至想用更复杂的方式来保存对象。为解决这个问题,Java 提供了四种类型的“集合类”:Vector(矢量)、BitSet(位集)、Stack(堆栈)以及Hashtable(散列表)。与拥有集合功能的其他语言相比,尽管这儿的数量显得相当少,但仍然能用它们解决数量惊人的实际问题。
这些集合类具有形形色色的特征。例如,Stack 实现了一个 LIFO(先入先出)序列,而 Hashtable 是一种“关联数组”,允许我们将任何对象关联起来。除此以外,所有Java 集合类都能自动改变自身的大小。所以,我们在编程时可使用数量众多的对象,同时不必担心会将集合弄得有多大。
2.1 缺点:类型未知
使用Java 集合的“缺点”是在将对象置入一个集合时丢失了类型信息。之所以会发生这种情况,是由于当初编写集合时,那个集合的程序员根本不知道用户到底想把什么类型置入集合。若指示某个集合只允许特定的类型,会妨碍它成为一个“常规用途”的工具,为用户带来麻烦。
为解决这个问题,集合实际容纳的是类型为Object 的一些对象的句柄。这种类型当然代表Java 中的所有对象,因为它是所有类的根。当然,也要注意这并不包括基本数据类型,因为它们并不是从“任何东西”继承来的。这是一个很好的方案,只是不适用下述场合:
(1) 将一个对象句柄置入集合时,由于类型信息会被抛弃,所以任何类型的对象都可进入我们的集合——即便特别指示它只能容纳特定类型的对象。举个例子来说,虽然指示它只能容纳猫,但事实上任何人都可以把一条狗扔进来。
(2) 由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。正式使用它之前,必须对其进行造型,使其具有正确的类型。值得欣慰的是,Java 不允许人们滥用置入集合的对象。假如将一条狗扔进一个猫的集合,那么仍会将集合内的所有东西都看作猫,所以在使用那条狗时会得到一个“违例”错误。在同样的意义上,假若试图将一条狗的句柄“造型”到一只猫,那么运行期间仍会得到一个“违例”错误。
下面是个例子:
import java.util.*;
class Cat {
privateintcatNumber;
Cat(inti) {
catNumber =i;
}
void print() {
System.out.println("Cat
#" + catNumber);
}
}
class Dog {
privateintdogNumber;
Dog(inti) {
dogNumber =i;
}
void print() {
System.out.println("Dog
#" + dogNumber);
}
}
publicclass CatsAndDogs {
publicstaticvoid
main(String[] args) {
Vectorcats =newVector();
for (inti
= 0; i < 7;i++)
cats.addElement(new Cat(i));
// Not a problem toadd a dog to cats:
cats.addElement(new
Dog(7));
for (inti
= 0; i <cats.size();i++)
((Cat)cats.elementAt(i)).print();
// Dog is detectedonly at run-time
}
} // /:~
输出:
Cat#0
Cat#1
Cat#2
Cat#3
Cat#4
Cat#5
Cat#6
Exceptionin thread "main"java.lang.ClassCastException: Dog incompatible with Cat
atjava.lang.ClassCastException.<init>(ClassCastException.java:58)
at CatsAndDogs.main(CatsAndDogs.java:35)
包含一个错误。
可以看出,Vector的使用是非常简单的:先创建一个,再用 addElement()置入对象,以后用 elementAt()取得那些对象(注意Vector 有一个 size()方法,可使我们知道已添加了多少个元素,以便防止误超边界,造成违例错误)。
Cat和Dog类都非常浅显——除了都是“对象”之外,它们并无特别之处(倘若不明确指出从什么类继承,就默认为从 Object继承。所以我们不仅能用Vector方法将 Cat对象置入这个集合,也能添加Dog对象,同时不会在编译期和运行期得到任何出错提示。用Vector 方法elementAt()获取原本认为是Cat 的对象时,实际获得的是指向一个Object 的句柄,必须将那个对象造型为Cat。随后,需要将整个表达式用括号封闭起来,在为Cat调用print()方法之前进行强制造型;否则就会出现一个语法错误。在运行期间,如果试图将Dog对象造型为
Cat,就会得到一个违例。
这些处理的意义都非常深远。尽管显得有些麻烦,但却获得了安全上的保证。我们从此再难偶然造成一些隐藏得深的错误。若程序的一个部分(或几个部分)将对象插入一个集合,但我们只是通过一次违例在程序的某个部分发现一个错误的对象置入了集合,就必须找出插入错误的位置。当然,可通过检查代码达到这个目的,但这或许是最笨的调试工具。另一方面,我们可从一些标准化的集合类开始自己的编程。尽管它们在功能上存在一些不足,且显得有些笨拙,但却能保证没有隐藏的错误。
2.1.1 错误有时并不显露出来
在某些情况下,程序似乎正确地工作,不造型回我们原来的类型。第一种情况是相当特殊的:String 类从编译器获得了额外的帮助,使其能够正常工作。只要编译器期待的是一个String 对象,但它没有得到一个,就会自动调用在Object 里定义、并且能够由任何Java 类覆盖的toString()方法。这个方法能生成满足要求的String对象,然后在我们需要的时候使用。
因此,为了让自己类的对象能显示出来,要做的全部事情就是覆盖toString()方法,
如下例所示:
import java.util.*;
class Mouse {
privateintmouseNumber;
Mouse(inti) {
mouseNumber =i;
}
// Magic method:
public String toString() {
return"This is Mouse #" +mouseNumber;
}
void print(Stringmsg)
{
if (msg
!= null)
System.out.println(msg);
System.out.println("Mouse
number" + mouseNumber);
}
}
class MouseTrap {
staticvoid caughtYa(Objectm)
{
Mousemouse = (Mouse)m;//
Cast from Object
mouse.print("Caught one!");
}
}
publicclass WorksAnyway {
publicstaticvoid
main(String[] args) {
Vectormice =newVector();
for (inti
= 0; i < 3;i++)
mice.addElement(new Mouse(i));
for (inti
= 0; i <mice.size();i++)
{
// No cast necessary,automatic call
// toObject.toString():
System.out.println("Free
mouse:" + mice.elementAt(i));
MouseTrap.caughtYa(mice.elementAt(i));
}
}
} // /:~
输出如下:
Freemouse: This is Mouse #0
Caughtone!
Mousenumber 0
Freemouse: This is Mouse #1
Caughtone!
Mousenumber 1
Freemouse: This is Mouse #2
Caughtone!
Mousenumber 2
可在Mouse 里看到对toString()的重定义代码。在main()的第二个for 循环中,可发现下述语句:
System.out.println("Free mouse: "+ mice.elementAt(i));
在“+”后,编译器预期看到的是一个String 对象。elementAt()生成了一个 Object,所以为获得希望的String,编译器会默认调用toString()。但不幸的是,只有针对String 才能得到象这样的结果;其他任何类型都不会进行这样的转换。
隐藏造型的第二种方法已在Mousetrap里得到了应用。caughtYa()方法接收的不是一个Mouse,而是一个Object。随后再将其造型为一个Mouse。当然,这样做是非常冒失的,因为通过接收一个 Object,任何东西都可以传递给方法。然而,假若造型不正确——如果我们传递了错误的类型——就会在运行期间得到一个违例错误。这当然没有在编译期进行检查好,但仍然能防止问题的发生。注意在使用这个方法时毋需进行造型:
MouseTrap.caughtYa(mice.elementAt(i));
2.1.2 生成能自动判别类型的 Vector
一个更“健壮”的方案是用Vector创建一个新类,使其只接收我们指定的类型,也只生成我们希望的类型。
如下所示:
import java.util.*;
class Gopher {
privateintgopherNumber;
Gopher(inti) {
gopherNumber =i;
}
void print(Stringmsg)
{
if (msg
!= null)
System.out.println(msg);
System.out.println("Gopher
number" + gopherNumber);
}
}
class GopherTrap {
staticvoid caughtYa(Gopherg)
{
g.print("Caught one!");
}
}
class GopherVector {
privateVector
v =newVector();
publicvoid addElement(Gopherm)
{
v.addElement(m);
}
public Gopher elementAt(intindex)
{
return (Gopher)v.elementAt(index);
}
publicint size() {
returnv.size();
}
publicstaticvoid
main(String[] args) {
GopherVectorgophers =new
GopherVector();
for (inti
= 0; i < 3;i++)
gophers.addElement(new Gopher(i));
for (inti
= 0; i <gophers.size();i++)
GopherTrap.caughtYa(gophers.elementAt(i));
}
} // /:~
输出如下:
Caughtone!
Gophernumber 0
Caughtone!
Gophernumber 1
Caughtone!
Gophernumber 2
GopherVector 类有一个类型为Vector的private成员(从Vector继承有些麻烦,理由稍后便知),而且方法也和Vector 类似。然而,它不会接收和产生普通Object,只对 Gopher对象感兴趣。
由于GopherVector只接收一个 Gopher(地鼠),所以假如我们使用:
gophers.addElement(new Pigeon());
就会在编译期间获得一条出错消息。采用这种方式,尽管从编码的角度看显得更令人沉闷,但可以立即判断出是否使用了正确的类型。
注意在使用elementAt()时不必进行造型——它肯定是一个Gopher。
3. 参数化类型
这类问题并不是孤立的——我们许多时候都要在其他类型的基础上创建新类型。此时,在编译期间拥有特定的类型信息是非常有帮助的。这便是“参数化类型”的概念。在C++中,它由语言通过“模板”获得了直接支持。至少,Java 保留了关键字generic,期望有一天能够支持参数化类型。但我们现在无法确定这一天何时会来临。