java 对象的容纳Word格式.docx
- 文档编号:20732291
- 上传时间:2023-01-25
- 格式:DOCX
- 页数:64
- 大小:71.30KB
java 对象的容纳Word格式.docx
《java 对象的容纳Word格式.docx》由会员分享,可在线阅读,更多相关《java 对象的容纳Word格式.docx(64页珍藏版)》请在冰豆网上搜索。
本章还要学习另外几种常见的集合类:
Vector(矢量)、Stack(堆栈)以及Hashtable(散列表)。
这些类都涉及对对象的处理——好象它们没有特定的类型。
换言之,它们将其当作Object类型处理(Object类型是Java中所有类的“根”类)。
从某个角度看,这种处理方法是非常合理的:
我们仅需构建一个集合,然后任何Java对象都可以进入那个集合(除基本数据类型外——可用Java的基本类型封装类将其作为常数置入集合,或者将其封装到自己的类内,作为可以变化的值使用)。
这再一次反映了数组优于常规集合:
创建一个数组时,可令其容纳一种特定的类型。
这意味着可进行编译期类型检查,预防自己设置了错误的类型,或者错误指定了准备提取的类型。
当然,在编译期或者运行期,Java会防止我们将不当的消息发给一个对象。
所以我们不必考虑自己的哪种做法更加危险,只要编译器能及时地指出错误,同时在运行期间加快速度,目的也就达到了。
此外,用户很少会对一次违例事件感到非常惊讶的。
考虑到执行效率和类型检查,应尽可能地采用数组。
然而,当我们试图解决一个更常规的问题时,数组的局限也可能显得非常明显。
在研究过数组以后,本章剩余的部分将把重点放到Java提供的集合类身上。
8.1.1数组和第一类对象
无论使用的数组属于什么类型,数组标识符实际都是指向真实对象的一个句柄。
那些对象本身是在内存“堆”里创建的。
堆对象既可“隐式”创建(即默认产生),亦可“显式”创建(即明确指定,用一个new表达式)。
堆对象的一部分(实际是我们能访问的唯一字段或方法)是只读的length(长度)成员,它告诉我们那个数组对象里最多能容纳多少元素。
对于数组对象,“[]”语法是我们能采用的唯一另类访问方法。
下面这个例子展示了对数组进行初始化的不同方式,以及如何将数组句柄分配给不同的数组对象。
它也揭示出对象数组和基本数据类型数组在使用方法上几乎是完全一致的。
唯一的差别在于对象数组容纳的是句柄,而基本数据类型数组容纳的是具体的数值(若在执行此程序时遇到困难,请参考第3章的“赋值”小节):
//:
c09:
ArraySize.java
//Initialization&
re-assignmentofarrays.
classWeeble{}//Asmallmythicalcreature
publicclassArraySize{
publicstaticvoidmain(String[]args){
//Arraysofobjects:
Weeble[]a;
//Nullreference
Weeble[]b=newWeeble[5];
//Nullreferences
Weeble[]c=newWeeble[4];
for(inti=0;
i<
c.length;
i++)
c[i]=newWeeble();
//Aggregateinitialization:
Weeble[]d={
newWeeble(),newWeeble(),newWeeble()
};
//Dynamicaggregateinitialization:
a=newWeeble[]{
newWeeble(),newWeeble()
System.out.println("
a.length="
+a.length);
b.length="
+b.length);
//Thereferencesinsidethearrayare
//automaticallyinitializedtonull:
b.length;
b["
+i+"
]="
+b[i]);
c.length="
+c.length);
d.length="
+d.length);
a=d;
a.length="
//Arraysofprimitives:
int[]e;
int[]f=newint[5];
int[]g=newint[4];
g.length;
g[i]=i*i;
int[]h={11,47,93};
//Compileerror:
variableenotinitialized:
//!
System.out.println("
e.length="
+e.length);
f.length="
+f.length);
//Theprimitivesinsidethearrayare
//automaticallyinitializedtozero:
f.length;
f["
+f[i]);
g.length="
+g.length);
h.length="
+h.length);
e=h;
e.length="
e=newint[]{1,2};
}
}///:
~
Here’stheoutputfromtheprogram:
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都指向内存堆内同样的数组对象。
Java1.1加入了一种新的数组初始化语法,可将其想象成“动态集合初始化”。
由d采用的Java1.0集合初始化方法则必须在定义d的同时进行。
但若采用Java1.1的语法,却可以在任何地方创建和初始化一个数组对象。
例如,假设hide()方法用于取得一个Weeble对象数组,那么调用它时传统的方法是:
hide(d);
但在Java1.1中,亦可动态创建想作为参数传递的数组,如下所示:
hide(newWeeble[]{newWeeble(),newWeeble()});
这一新式语法使我们在某些场合下写代码更方便了。
上述例子的第二部分揭示出这样一个问题:
对于由基本数据类型构成的数组,它们的运作方式与对象数组极为相似,只是前者直接包容了基本类型的数据值。
1.基本数据类型集合
集合类只能容纳对象句柄。
但对一个数组,却既可令其直接容纳基本类型的数据,亦可容纳指向对象的句柄。
利用象Integer、Double之类的“封装器”类,可将基本数据类型的值置入一个集合里。
但正如本章后面会在WordCount.java例子中讲到的那样,用于基本数据类型的封装器类只是在某些场合下才能发挥作用。
无论将基本类型的数据置入数组,还是将其封装进入位于集合的一个类内,都涉及到执行效率的问题。
显然,若能创建和访问一个基本数据类型数组,那么比起访问一个封装数据的集合,前者的效率会高出许多。
当然,假如准备一种基本数据类型,同时又想要集合的灵活性(在需要的时候可自动扩展,腾出更多的空间),就不宜使用数组,必须使用由封装的数据构成的一个集合。
大家或许认为针对每种基本数据类型,都应有一种特殊类型的Vector。
但Java并未提供这一特性。
某些形式的建模机制或许会在某一天帮助Java更好地解决这个问题(注释①)。
①:
这儿是C++比Java做得好的一个地方,因为C++通过template关键字提供了对“参数化类型”的支持。
8.1.2数组的返回
假定我们现在想写一个方法,同时不希望它仅仅返回一样东西,而是想返回一系列东西。
此时,象C和C++这样的语言会使问题复杂化,因为我们不能返回一个数组,只能返回指向数组的一个指针。
这样就非常麻烦,因为很难控制数组的“存在时间”,它很容易造成内存“漏洞”的出现。
Java采用的是类似的方法,但我们能“返回一个数组”。
当然,此时返回的实际仍是指向数组的指针。
但在Java里,我们永远不必担心那个数组的是否可用——只要需要,它就会自动存在。
而且垃圾收集器会在我们完成后自动将其清除。
作为一个例子,请思考如何返回一个字串数组:
IceCream.java
//Returningarraysfrommethods.
publicclassIceCream{
staticString[]flav={
"
Chocolate"
"
Strawberry"
VanillaFudgeSwirl"
MintChip"
MochaAlmondFudge"
RumRaisin"
PralineCream"
MudPie"
staticString[]flavorSet(intn){
//Forceittobepositive&
withinbounds:
n=Math.abs(n)%(flav.length+1);
String[]results=newString[n];
boolean[]picked=
newboolean[flav.length];
for(inti=0;
n;
i++){
intt;
do
t=(int)(Math.random()*flav.length);
while(picked[t]);
results[i]=flav[t];
picked[t]=true;
returnresults;
20;
System.out.println(
flavorSet("
)="
);
String[]fl=flavorSet(flav.length);
for(intj=0;
j<
fl.length;
j++)
\t"
+fl[j]);
flavorSet()方法创建了一个名为results的String数组。
该数组的大小为n——具体数值取决于我们传递给方法的自变量。
随后,它从数组flav里随机挑选一些“香料”(Flavor),并将它们置入results里,并最终返回results。
返回数组与返回其他任何对象没什么区别——最终返回的都是一个句柄。
至于数组到底是在flavorSet()里创建的,还是在其他什么地方创建的,这个问题并不重要,因为反正返回的仅是一个句柄。
一旦我们的操作完成,垃圾收集器会自动关照数组的清除工作。
而且只要我们需要数组,它就会乖乖地听候调遣。
另一方面,注意当flavorSet()随机挑选香料的时候,它需要保证以前出现过的一次随机选择不会再次出现。
为达到这个目的,它使用了一个无限while循环,不断地作出随机选择,直到发现未在picks数组里出现过的一个元素为止(当然,也可以进行字串比较,检查随机选择是否在results数组里出现过,但字串比较的效率比较低)。
若成功,就添加这个元素,并中断循环(break),再查找下一个(i值会递增)。
但假若t是一个已在picks里出现过的数组,就用标签式的continue往回跳两级,强制选择一个新t。
用一个调试程序可以很清楚地看到这个过程。
main()能显示出20个完整的香料集合,所以我们看到flavorSet()每次都用一个随机顺序选择香料。
为体会这一点,最简单的方法就是将输出重导向进入一个文件,然后直接观看这个文件的内容。
8.2集合
现在总结一下我们前面学过的东西:
为容纳一组对象,最适宜的选择应当是数组。
而且假如容纳的是一系列基本数据类型,更是必须采用数组。
在本章剩下的部分,大家将接触到一些更常规的情况。
当我们编写程序时,通常并不能确切地知道最终需要多少个对象。
有些时候甚至想用更复杂的方式来保存对象。
为解决这个问题,Java提供了四种类型的“集合类”:
Vector(矢量)、BitSet(位集)、Stack(堆栈)以及Hashtable(散列表)。
与拥有集合功能的其他语言相比,尽管这儿的数量显得相当少,但仍然能用它们解决数量惊人的实际问题。
这些集合类具有形形色色的特征。
例如,Stack实现了一个LIFO(先入先出)序列,而Hashtable是一种“关联数组”,允许我们将任何对象关联起来。
除此以外,所有Java集合类都能自动改变自身的大小。
所以,我们在编程时可使用数量众多的对象,同时不必担心会将集合弄得有多大。
8.2.1缺点:
类型未知
使用Java集合的“缺点”是在将对象置入一个集合时丢失了类型信息。
之所以会发生这种情况,是由于当初编写集合时,那个集合的程序员根本不知道用户到底想把什么类型置入集合。
若指示某个集合只允许特定的类型,会妨碍它成为一个“常规用途”的工具,为用户带来麻烦。
为解决这个问题,集合实际容纳的是类型为Object的一些对象的句柄。
这种类型当然代表Java中的所有对象,因为它是所有类的根。
当然,也要注意这并不包括基本数据类型,因为它们并不是从“任何东西”继承来的。
这是一个很好的方案,只是不适用下述场合:
(1)将一个对象句柄置入集合时,由于类型信息会被抛弃,所以任何类型的对象都可进入我们的集合——即便特别指示它只能容纳特定类型的对象。
举个例子来说,虽然指示它只能容纳猫,但事实上任何人都可以把一条狗扔进来。
(2)由于类型信息不复存在,所以集合能肯定的唯一事情就是自己容纳的是指向一个对象的句柄。
正式使用它之前,必须对其进行造型,使其具有正确的类型。
值得欣慰的是,Java不允许人们滥用置入集合的对象。
假如将一条狗扔进一个猫的集合,那么仍会将集合内的所有东西都看作猫,所以在使用那条狗时会得到一个“违例”错误。
在同样的意义上,假若试图将一条狗的句柄“造型”到一只猫,那么运行期间仍会得到一个“违例”错误。
下面是个例子:
First,CatandDogclassesarecreated:
Cat.java
publicclassCat{
privateintcatNumber;
Cat(inti){catNumber=i;
voidprint(){
Cat#"
+catNumber);
Dog.java
publicclassDog{
privateintdogNumber;
Dog(inti){dogNumber=i;
Dog#"
+dogNumber);
CatsandDogsareplacedintothecontainer,thenpulledout:
CatsAndDogs.java
//Simplecontainerexample.
importjava.util.*;
publicclassCatsAndDogs{
ArrayListcats=newArrayList();
7;
cats.add(newCat(i));
//Notaproblemtoaddadogtocats:
cats.add(newDog(7));
cats.size();
((Cat)cats.get(i)).print();
//Dogisdetectedonlyatrun-time
可以看出,Vector的使用是非常简单的:
先创建一个,再用addElement()置入对象,以后用elementAt()取得那些对象(注意Vector有一个size()方法,可使我们知道已添加了多少个元素,以便防止误超边界,造成违例错误)。
Cat和Dog类都非常浅显——除了都是“对象”之外,它们并无特别之处(倘若不明确指出从什么类继承,就默认为从Object继承。
所以我们不仅能用Vector方法将Cat对象置入这个集合,也能添加Dog对象,同时不会在编译期和运行期得到任何出错提示。
用Vector方法elementAt()获取原本认为是Cat的对象时,实际获得的是指向一个Object的句柄,必须将那个对象造型为Cat。
随后,需要将整个表达式用括号封闭起来,在为Cat调用print()方法之前进行强制造型;
否则就会出现一个语法错误。
在运行期间,如果试图将Dog对象造型为Cat,就会得到一个违例。
这些处理的意义都非常深远。
尽管显得有些麻烦,但却获得了安全上的保证。
我们从此再难偶然造成一些隐藏得深的错误。
若程序的一个部分(或几个部分)将对象插入一个集合,但我们只是通过一次违例在程序的某个部分发现一个错误的对象置入了集合,就必须找出插入错误的位置。
当然,可通过检查代码达到这个目的,但这或许是最笨的调试工具。
另一方面,我们可从一些标准化的集合类开始自己的编程。
尽管它们在功能上存在一些不足,且显得有些笨拙,但却能保证没有隐藏的错误。
1.错误有时并不显露出来
在某些情况下,程序似乎正确地工作,不造型回我们原来的类型。
第一种情况是相当特殊的:
String类从编译器获得了额外的帮助,使其能够正常工作。
只要编译器期待的是一个String对象,但它没有得到一个,就会自动调用在Object里定义、并且能够由任何Java类覆盖的toString()方法。
这个方法能生成满足要求的String对象,然后在我们需要的时候使用。
因此,为了让自己类的对象能显示出来,要做的全部事情就是覆盖toString()方法,如下例所示:
M
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- java 对象的容纳 对象 容纳