java编程思想第二章.docx

上传人:b****8 文档编号:9788509 上传时间:2023-02-06 格式:DOCX 页数:35 大小:37.96KB
下载 相关 举报
java编程思想第二章.docx_第1页
第1页 / 共35页
java编程思想第二章.docx_第2页
第2页 / 共35页
java编程思想第二章.docx_第3页
第3页 / 共35页
java编程思想第二章.docx_第4页
第4页 / 共35页
java编程思想第二章.docx_第5页
第5页 / 共35页
点击查看更多>>
下载资源
资源描述

java编程思想第二章.docx

《java编程思想第二章.docx》由会员分享,可在线阅读,更多相关《java编程思想第二章.docx(35页珍藏版)》请在冰豆网上搜索。

java编程思想第二章.docx

java编程思想第二章

第2章一切都是对象

“尽管以C++为基础,但Java是一种更纯粹的面向对象程序设计语言”。

无论C++还是Java都属于杂合语言。

但在Java中,设计者觉得这种杂合并不象在C++里那么重要。

杂合语言

允许采用多种编程风格;之所以说C++是一种杂合语言,是因为它支持与C语言的向后兼容能力。

由于C++是

C的一个超集,所以包含的许多特性都是后者不具备的,这些特性使C++在某些地方显得过于复杂。

Java语言首先便假定了我们只希望进行面向对象的程序设计。

也就是说,正式用它设计之前,必须先将自己

的思想转入一个面向对象的世界(除非早已习惯了这个世界的思维方式)。

只有做好这个准备工作,与其他

OOP语言相比,才能体会到Java的易学易用。

在本章,我们将探讨Java程序的基本组件,并体会为什么说

Java乃至Java程序内的一切都是对象。

2.1用句柄操纵对象

每种编程语言都有自己的数据处理方式。

有些时候,程序员必须时刻留意准备处理的是什么类型。

您曾利用

一些特殊语法直接操作过对象,或处理过一些间接表示的对象吗(C或C++里的指针)?

所有这些在Java里都得到了简化,任何东西都可看作对象。

因此,我们可采用一种统一的语法,任何地方均

可照搬不误。

但要注意,尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“句柄”

(Handle)。

在其他Java参考书里,还可看到有的人将其称作一个“引用”,甚至一个“指针”。

可将这一

情形想象成用遥控板(句柄)操纵电视机(对象)。

只要握住这个遥控板,就相当于掌握了与电视机连接的

通道。

但一旦需要“换频道”或者“关小声音”,我们实际操纵的是遥控板(句柄),再由遥控板自己操纵

电视机(对象)。

如果要在房间里四处走走,并想保持对电视机的控制,那么手上拿着的是遥控板,而非电

视机。

此外,即使没有电视机,遥控板亦可独立存在。

也就是说,只是由于拥有一个句柄,并不表示必须有一个对

象同它连接。

所以如果想容纳一个词或句子,可创建一个String句柄:

Strings;

但这里创建的只是句柄,并不是对象。

若此时向s发送一条消息,就会获得一个错误(运行期)。

这是由于

s实际并未与任何东西连接(即“没有电视机”)。

因此,一种更安全的做法是:

创建一个句柄时,记住无

论如何都进行初始化:

Strings="asdf";

然而,这里采用的是一种特殊类型:

字串可用加引号的文字初始化。

通常,必须为对象使用一种更通用的初

始化类型。

2.2所有对象都必须创建

创建句柄时,我们希望它同一个新对象连接。

通常用new关键字达到这一目的。

new的意思是:

“把我变成

这些对象的一种新类型”。

所以在上面的例子中,可以说:

Strings=newString("asdf");

它不仅指出“将我变成一个新字串”,也通过提供一个初始字串,指出了“如何生成这个新字串”。

当然,字串(String)并非唯一的类型。

Java配套提供了数量众多的现成类型。

对我们来讲,最重要的就是

记住能自行创建类型。

事实上,这应是Java程序设计的一项基本操作,是继续本书后余部分学习的基础。

2.2.1保存到什么地方

程序运行时,我们最好对数据保存到什么地方做到心中有数。

特别要注意的是内存的分配。

有六个地方都可

以保存数据:

(1)寄存器。

这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:

处理器内部。

然而,寄存

器的数量十分有限,所以寄存器是根据需要由编译器分配。

我们对此没有直接的控制权,也不可能在自己的

程序里找到寄存器存在的任何踪迹。

(2)堆栈。

驻留于常规RAM(随机访问存储器)区域,但可通过它的“堆栈指针”获得处理的直接支持。

栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。

这是一种特别快、特别有效的数据保存

方式,仅次于寄存器。

创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存

在时间”。

这是由于它必须生成相应的代码,以便向上和向下移动指针。

这一限制无疑影响了程序的灵活

性,所以尽管有些Java数据要保存在堆栈里——特别是对象句柄,但Java对象并不放到其中。

47

(3)堆。

一种常规用途的内存池(也在RAM区域),其中保存了Java对象。

和堆栈不同,“内存堆”或

“堆”(Heap)最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要

在堆里停留多长的时间。

因此,用堆保存数据时会得到更大的灵活性。

要求创建一个对象时,只需用new命

令编制相关的代码即可。

执行这些代码时,会在堆里自动进行数据的保存。

当然,为达到这种灵活性,必然

会付出一定的代价:

在堆里分配存储空间时会花掉更长的时间!

(4)静态存储。

这儿的“静态”(Static)是指“位于固定位置”(尽管也在RAM里)。

程序运行期间,静

态存储的数据将随时等候调用。

可用static关键字指出一个对象的特定元素是静态的。

但Java对象本身永

远都不会置入静态存储空间。

(5)常数存储。

常数值通常直接置于程序代码内部。

这样做是安全的,因为它们永远都不会改变。

有的常数

需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。

(6)非RAM存储。

若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。

其中两个最主要的例子便是“流式对象”和“固定对象”。

对于流式对象,对象会变成字节流,通常会发给

另一台机器。

而对于固定对象,对象保存在磁盘中。

即使程序中止运行,它们仍可保持自己的状态不变。

于这些类型的数据存储,一个特别有用的技巧就是它们能存在于其他媒体中。

一旦需要,甚至能将它们恢复

成普通的、基于RAM的对象。

Java1.1提供了对Lightweightpersistence的支持。

未来的版本甚至可能提

供更完整的方案。

2.2.2特殊情况:

主要类型

有一系列类需特别对待;可将它们想象成“基本”、“主要”或者“主”(Primitive)类型,进行程序设计

时要频繁用到它们。

之所以要特别对待,是由于用new创建对象(特别是小的、简单的变量)并不是非常有

效,因为new将对象置于“堆”里。

对于这些类型,Java采纳了与C和C++相同的方法。

也就是说,不是用

new创建变量,而是创建一个并非句柄的“自动”变量。

这个变量容纳了具体的值,并置于堆栈中,能够更

高效地存取。

Java决定了每种主要类型的大小。

就象在大多数语言里那样,这些大小并不随着机器结构的变化而变化。

种大小的不可更改正是Java程序具有很强移植能力的原因之一。

主类型大小最小值最大值封装器类型

boolean1位--Boolean

char16位Unicode0Unicode2的16次方-1Character

byte8位-128+127Byte(注释①)

short16位-2的15次方+2的15次方-1Short(注释①)

int32位-2的31次方+2的31次方-1Integer

long64位-2的63次方+2的63次方-1Long

float32位IEEE754IEEE754Float

double64位IEEE754IEEE754Double

Void---Void(注释①)

①:

到Java1.1才有,1.0版没有。

数值类型全都是有符号(正负号)的,所以不必费劲寻找没有符号的类型。

主数据类型也拥有自己的“封装器”(wrapper)类。

这意味着假如想让堆内一个非主要对象表示那个主类

型,就要使用对应的封装器。

例如:

charc='x';

CharacterC=newCharacter('c');

也可以直接使用:

CharacterC=newCharacter('x');

这样做的原因将在以后的章节里解释。

1.高精度数字

Java1.1增加了两个类,用于进行高精度的计算:

BigInteger和BigDecimal。

尽管它们大致可以划分为

“封装器”类型,但两者都没有对应的“主类型”。

48

这两个类都有自己特殊的“方法”,对应于我们针对主类型执行的操作。

也就是说,能对int或float做的

事情,对BigInteger和BigDecimal一样可以做。

只是必须使用方法调用,不能使用运算符。

此外,由于牵

涉更多,所以运算速度会慢一些。

我们牺牲了速度,但换来了精度。

BigInteger支持任意精度的整数。

也就是说,我们可精确表示任意大小的整数值,同时在运算过程中不会丢

失任何信息。

BigDecimal支持任意精度的定点数字。

例如,可用它进行精确的币值计算。

至于调用这两个类时可选用的构建器和方法,请自行参考联机帮助文档。

2.2.3Java的数组

几乎所有程序设计语言都支持数组。

在C和C++里使用数组是非常危险的,因为那些数组只是内存块。

若程

序访问自己内存块以外的数组,或者在初始化之前使用内存(属于常规编程错误),会产生不可预测的后果

(注释②)。

②:

在C++里,应尽量不要使用数组,换用标准模板库(StandardTemplateLibrary)里更安全的容器。

Java的一项主要设计目标就是安全性。

所以在C和C++里困扰程序员的许多问题都未在Java里重复。

一个

Java可以保证被初始化,而且不可在它的范围之外访问。

由于系统自动进行范围检查,所以必然要付出一些

代价:

针对每个数组,以及在运行期间对索引的校验,都会造成少量的内存开销。

但由此换回的是更高的安

全性,以及更高的工作效率。

为此付出少许代价是值得的。

创建对象数组时,实际创建的是一个句柄数组。

而且每个句柄都会自动初始化成一个特殊值,并带有自己的

关键字:

null(空)。

一旦Java看到null,就知道该句柄并未指向一个对象。

正式使用前,必须为每个句

柄都分配一个对象。

若试图使用依然为null的一个句柄,就会在运行期报告问题。

因此,典型的数组错误在

Java里就得到了避免。

也可以创建主类型数组。

同样地,编译器能够担保对它的初始化,因为会将那个数组的内存划分成零。

数组问题将在以后的章节里详细讨论。

2.3绝对不要清除对象

在大多数程序设计语言中,变量的“存在时间”(Lifetime)一直是程序员需要着重考虑的问题。

变量应持

续多长的时间?

如果想清除它,那么何时进行?

在变量存在时间上纠缠不清会造成大量的程序错误。

在下面

的小节里,将阐示Java如何帮助我们完成所有清除工作,从而极大了简化了这个问题。

2.3.1作用域

大多数程序设计语言都提供了“作用域”(Scope)的概念。

对于在作用域里定义的名字,作用域同时决定了

它的“可见性”以及“存在时间”。

在C,C++和Java里,作用域是由花括号的位置决定的。

参考下面这个

例子:

{

intx=12;

/*onlyxavailable*/

{

intq=96;

/*bothx&qavailable*/

}

/*onlyxavailable*/

/*q“outofscope”*/

}

作为在作用域里定义的一个变量,它只有在那个作用域结束之前才可使用。

在上面的例子中,缩进排版使Java代码更易辨读。

由于Java是一种形式自由的语言,所以额外的空格、制

表位以及回车都不会对结果程序造成影响。

注意尽管在C和C++里是合法的,但在Java里不能象下面这样书写代码:

49

{

intx=12;

{

intx=96;/*illegal*/

}

}

编译器会认为变量x已被定义。

所以C和C++能将一个变量“隐藏”在一个更大的作用域里。

但这种做法在

Java里是不允许的,因为Java的设计者认为这样做使程序产生了混淆。

2.3.2对象的作用域

Java对象不具备与主类型一样的存在时间。

用new关键字创建一个Java对象的时候,它会超出作用域的范

围之外。

所以假若使用下面这段代码:

{

Strings=newString("astring");

}/*作用域的终点*/

那么句柄s会在作用域的终点处消失。

然而,s指向的String对象依然占据着内存空间。

在上面这段代码

里,我们没有办法访问对象,因为指向它的唯一一个句柄已超出了作用域的边界。

在后面的章节里,大家还

会继续学习如何在程序运行期间传递和复制对象句柄。

这样造成的结果便是:

对于用new创建的对象,只要我们愿意,它们就会一直保留下去。

这个编程问题在C

和C++里特别突出。

看来在C++里遇到的麻烦最大:

由于不能从语言获得任何帮助,所以在需要对象的时候,

根本无法确定它们是否可用。

而且更麻烦的是,在C++里,一旦工作完成,必须保证将对象清除。

这样便带来了一个有趣的问题。

假如Java让对象依然故我,怎样才能防止它们大量充斥内存,并最终造成程

序的“凝固”呢。

在C++里,这个问题最令程序员头痛。

但Java以后,情况却发生了改观。

Java有一个特别

的“垃圾收集器”,它会查找用new创建的所有对象,并辨别其中哪些不再被引用。

随后,它会自动释放由

那些闲置对象占据的内存,以便能由新对象使用。

这意味着我们根本不必操心内存的回收问题。

只需简单地

创建对象,一旦不再需要它们,它们就会自动离去。

这样做可防止在C++里很常见的一个编程问题:

由于程

序员忘记释放内存造成的“内存溢出”。

2.4新建数据类型:

如果说一切东西都是对象,那么用什么决定一个“类”(Class)的外观与行为呢?

换句话说,是什么建立起

了一个对象的“类型”(Type)呢?

大家可能猜想有一个名为“type”的关键字。

但从历史看来,大多数面

向对象的语言都用关键字“class”表达这样一个意思:

“我准备告诉你对象一种新类型的外观”。

class关

键字太常用了,以至于本书许多地方并没有用粗体字或双引号加以强调。

在这个关键字的后面,应该跟随新

数据类型的名称。

例如:

classATypeName{/*类主体置于这里}

这样就引入了一种新类型,接下来便可用new创建这种类型的一个新对象:

ATypeNamea=newATypeName();

在ATypeName里,类主体只由一条注释构成(星号和斜杠以及其中的内容,本章后面还会详细讲述),所以

并不能对它做太多的事情。

事实上,除非为其定义了某些方法,否则根本不能指示它做任何事情。

2.4.1字段和方法

定义一个类时(我们在Java里的全部工作就是定义类、制作那些类的对象以及将消息发给那些对象),可在

自己的类里设置两种类型的元素:

数据成员(有时也叫“字段”)以及成员函数(通常叫“方法”)。

中,数据成员是一种对象(通过它的句柄与其通信),可以为任何类型。

它也可以是主类型(并不是句柄)

之一。

如果是指向对象的一个句柄,则必须初始化那个句柄,用一种名为“构建器”(第4章会对此详述)

的特殊函数将其与一个实际对象连接起来(就象早先看到的那样,使用new关键字)。

但若是一种主类型,

则可在类定义位置直接初始化(正如后面会看到的那样,句柄亦可在定义位置初始化)。

50

每个对象都为自己的数据成员保有存储空间;数据成员不会在对象之间共享。

下面是定义了一些数据成员的

类示例:

classDataOnly{

inti;

floatf;

booleanb;

}

这个类并没有做任何实质性的事情,但我们可创建一个对象:

DataOnlyd=newDataOnly();

可将值赋给数据成员,但首先必须知道如何引用一个对象的成员。

为达到引用对象成员的目的,首先要写上

对象句柄的名字,再跟随一个点号(句点),再跟随对象内部成员的名字。

即“对象句柄.成员”。

例如:

d.i=47;

d.f=1.1f;

d.b=false;

一个对象也可能包含了另一个对象,而另一个对象里则包含了我们想修改的数据。

对于这个问题,只需保持

“连接句点”即可。

例如:

myPlane.leftTank.capacity=100;

除容纳数据之外,DataOnly类再也不能做更多的事情,因为它没有成员函数(方法)。

为正确理解工作原

理,首先必须知道“自变量”和“返回值”的概念。

我们马上就会详加解释。

1.主成员的默认值

若某个主数据类型属于一个类成员,那么即使不明确(显式)进行初始化,也可以保证它们获得一个默认

值。

主类型默认值

Booleanfalse

Char'\u0000'(null)

byte(byte)0

short(short)0

int0

long0L

float0.0f

double0.0d

一旦将变量作为类成员使用,就要特别注意由Java分配的默认值。

这样做可保证主类型的成员变量肯定得到

了初始化(C++不具备这一功能),可有效遏止多种相关的编程错误。

然而,这种保证却并不适用于“局部”变量——那些变量并非一个类的字段。

所以,假若在一个函数定义中

写入下述代码:

intx;

那么x会得到一些随机值(这与C和C++是一样的),不会自动初始化成零。

我们责任是在正式使用x前分

配一个适当的值。

如果忘记,就会得到一条编译期错误,告诉我们变量可能尚未初始化。

这种处理正是Java

优于C++的表现之一。

许多C++编译器会对变量未初始化发出警告,但在Java里却是错误。

2.5方法、自变量和返回值

迄今为止,我们一直用“函数”(Function)这个词指代一个已命名的子例程。

但在Java里,更常用的一个

词却是“方法”(Method),代表“完成某事的途径”。

尽管它们表达的实际是同一个意思,但从现在开

始,本书将一直使用“方法”,而不是“函数”。

Java的“方法”决定了一个对象能够接收的消息。

通过本节的学习,大家会知道方法的定义有多么简单!

方法的基本组成部分包括名字、自变量、返回类型以及主体。

下面便是它最基本的形式:

51

返回类型方法名(/*自变量列表*/){/*方法主体*/}

返回类型是指调用方法之后返回的数值类型。

显然,方法名的作用是对具体的方法进行标识和引用。

自变量

列表列出了想传递给方法的信息类型和名称。

Java的方法只能作为类的一部分创建。

只能针对某个对象调用一个方法(注释③),而且那个对象必须能够

执行那个方法调用。

若试图为一个对象调用错误的方法,就会在编译期得到一条出错消息。

为一个对象调用

方法时,需要先列出对象的名字,在后面跟上一个句点,再跟上方法名以及它的参数列表。

亦即“对象名.方

法名(自变量1,自变量2,自变量3...)。

举个例子来说,假设我们有一个方法名叫f(),它没有自变量,返

回的是类型为int的一个值。

那么,假设有一个名为a的对象,可为其调用方法f(),则代码如下:

intx=a.f();

返回值的类型必须兼容x的类型。

象这样调用一个方法的行动通常叫作“向对象发送一条消息”。

在上面的例子中,消息是f(),而对象是a。

面向对象的程序设计通常简单地归纳为“向对象发送消息”。

③:

正如马上就要学到的那样,“静态”方法可针对类调用,毋需一个对象。

2.5.1自变量列表

自变量列表规定了我们传送给方法的是什么信息。

正如大家或许已猜到的那样,这些信息——如同Java内其

他任何东西——采用的都是对象的形式。

因此,我们必须在自变量列表里指定要传递的对象类型,以及每个

对象的名字。

正如在Java其他地方处理对象时一样,我们实际传递的是“句柄”(注释④)。

然而,句柄的

类型必须正确。

倘若希望自变量是一个“字串”,那么传递的必须是一个字串。

④:

对于前面提及的“特殊”数据类型boolean,char,byte,short,int,long,,float以及double来

说是一个例外。

但在传递对象时,通常都是指传递指向对象的句柄。

下面让我们考虑将一个字串作为自变量使用的方法。

下面列出的是定义代码,必须将它置于一个类定义里,

否则无法编译:

intstorage(Strings){

returns.length()*2;

}

这个方法告诉我们需要多少字节才能容纳一个特定字串里的信息(字串里的每个字符都是16位,或者说2个

字节、长整数,以便提供对Unicode字符的支持)。

自变量的类型为String,而且叫作s。

一旦将s传递给

方法,就可将它当作其他对象一样处理(可向其发送消息)。

在这里,我们调用的是length()方法,它是

String的方法之一。

该方法返回的是一个字串里的字符数。

通过上面的例子,也可以了解return关键字的运用。

它主要做两件事情。

首先,它意味着“离开方法,我已

完工了”。

其次,假设方法生成了一个值,则那个值紧接在return语句的后面。

在这种情况下,返回值是通

过计算表达式“s.length()*2”而产生的。

可按自己的愿望返回任意类型,但倘若不想返回任何东西,就可指示方法返回void(空)。

下面列出一些例

子。

booleanflag(){returntrue;}

floatnaturalLogBase(){return2.718;}

voidnothing(){return;}

voidnothing2(){}

若返回类型为void,则return关键字唯一的作用就是退出方法。

所以一旦抵达方法末尾,该关键字便不需

要了。

可在任何地方从一个方法返回。

但假设已指定了一种非void的返回类型,那么无论从何地返回,编译

器都会确保我们返回的是正确的类型。

到此为止,大家或许已得到了这样的一个印象:

一个程序只是一系列对象的集合,它们的方法将其他对象作

52

为自己的自变量使用,而且将消息发给那些对象。

这种说法大体正确,但通过以后的学习,大家还会知道如

何在一个方法里作出决策,做一些更细致的基层工作。

至于这一章,只需理解消息传送就足够了。

2.6构建Java程序

正式构建自己的第一个Java程序前,还有几个问题需要注意。

2.6.1名字的可见性

在所有程序设计语言里,一个不可避免的问题是对名字或名称的控制。

假设您在程序的某个模块里使用了一

个名字,而另一名程序员在另一个模块里使用了相同的名字。

此时,如何区分两个名字,并防止两个名字互

相冲突呢?

这个问题在C语言里特别突出。

因为程序未提供很好的名字管理方法。

C++的类(即Java类的基

础)嵌套使用类里的函数,使其不至于同其他类里的嵌套函数名冲突。

然而,C++仍然允许使用全局数据以及

全局函数,所以仍然难以避免冲突。

为解决这个问题,C++用额外的关键字引入了“命名空间”的概念。

由于采用全新的机制,所以Java能完全避免这些问题。

为了给一个库生成明确的名字,采用了与Internet

域名类似的名字。

事实上,Java的设计者鼓励程序员反转使用自己的Internet域名,因为它们肯定是独一

无二的。

由于我的域

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 考试认证 > 从业资格考试

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1